-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Close #3451 Signed-off-by: Ekaterina Pavlova <[email protected]>
- Loading branch information
1 parent
7766168
commit 40c7142
Showing
5 changed files
with
307 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
// Package nep11 provides RPC wrappers for NEP-11 contracts, including support for NEP-24 NFT royalties. | ||
package nep11 | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" | ||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" | ||
"github.com/nspcc-dev/neo-go/pkg/util" | ||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" | ||
) | ||
|
||
// RoyaltyInfoDetail contains information about the recipient and the royalty amount. | ||
type RoyaltyInfoDetail struct { | ||
RoyaltyRecipient util.Uint160 | ||
RoyaltyAmount *big.Int | ||
} | ||
|
||
// RoyaltiesTransferredEvent represents a RoyaltiesTransferred event as defined in the NEP-24 standard. | ||
type RoyaltiesTransferredEvent struct { | ||
RoyaltyToken util.Uint160 | ||
RoyaltyRecipient util.Uint160 | ||
Buyer util.Uint160 | ||
TokenID []byte | ||
Amount *big.Int | ||
} | ||
|
||
// RoyaltyReader is an interface for contracts implementing NEP-24 royalties. | ||
type RoyaltyReader struct { | ||
BaseReader | ||
} | ||
|
||
// RoyaltyWriter is an interface for state-changing methods related to NEP-24 royalties. | ||
type RoyaltyWriter struct { | ||
BaseWriter | ||
} | ||
|
||
// Royalty is a full reader and writer interface for NEP-24 royalties. | ||
type Royalty struct { | ||
RoyaltyReader | ||
RoyaltyWriter | ||
} | ||
|
||
// NewRoyaltyReader creates an instance of RoyaltyReader for a contract with the given hash using the given invoker. | ||
func NewRoyaltyReader(invoker Invoker, hash util.Uint160) *RoyaltyReader { | ||
return &RoyaltyReader{*NewBaseReader(invoker, hash)} | ||
} | ||
|
||
// NewRoyalty creates an instance of Royalty for a contract with the given hash using the given actor. | ||
func NewRoyalty(actor Actor, hash util.Uint160) *Royalty { | ||
return &Royalty{*NewRoyaltyReader(actor, hash), RoyaltyWriter{BaseWriter{hash, actor}}} | ||
} | ||
|
||
// RoyaltyInfo retrieves the royalty information for a given token ID, including the recipient(s) and amount(s). | ||
func (r *RoyaltyReader) RoyaltyInfo(tokenID []byte, royaltyToken util.Uint160, salePrice *big.Int) ([]*RoyaltyInfoDetail, error) { | ||
items, err := unwrap.Array(r.invoker.Call(r.hash, "RoyaltyInfo", tokenID, royaltyToken, salePrice)) | ||
fmt.Println(items) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
royalties := make([]*RoyaltyInfoDetail, len(items)) | ||
for i, item := range items { | ||
royaltyDetail, err := itemToRoyaltyInfoDetail(item, nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode royalty detail %d: %w", i, err) | ||
} | ||
royalties[i] = royaltyDetail | ||
} | ||
return royalties, nil | ||
} | ||
|
||
// itemToRoyaltyInfoDetail converts a stack item into a RoyaltyInfoDetail struct. | ||
func itemToRoyaltyInfoDetail(item stackitem.Item, err error) (*RoyaltyInfoDetail, error) { | ||
if err != nil { | ||
return nil, err | ||
} | ||
fmt.Println("itemToRoyaltyInfoDetail item", item) | ||
|
||
arr, ok := item.Value().([]stackitem.Item) | ||
if !ok || len(arr) != 2 { | ||
return nil, fmt.Errorf("invalid RoyaltyInfoDetail structure: expected array of 2 items, got %T", item.Value()) | ||
} | ||
|
||
recipientBytes, err := arr[0].TryBytes() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode RoyaltyRecipient: %w", err) | ||
} | ||
|
||
recipient, err := util.Uint160DecodeBytesBE(recipientBytes) | ||
if err != nil { | ||
return nil, fmt.Errorf("invalid RoyaltyRecipient: %w", err) | ||
} | ||
|
||
amount, err := arr[1].TryInteger() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode RoyaltyAmount: %w", err) | ||
} | ||
|
||
return &RoyaltyInfoDetail{ | ||
RoyaltyRecipient: recipient, | ||
RoyaltyAmount: amount, | ||
}, nil | ||
} | ||
|
||
// RoyaltiesTransferredEventsFromApplicationLog retrieves all emitted RoyaltiesTransferredEvents from the provided [result.ApplicationLog]. | ||
func RoyaltiesTransferredEventsFromApplicationLog(log *result.ApplicationLog) ([]*RoyaltiesTransferredEvent, error) { | ||
if log == nil { | ||
return nil, errors.New("nil application log") | ||
} | ||
var res []*RoyaltiesTransferredEvent | ||
for i, ex := range log.Executions { | ||
for j, e := range ex.Events { | ||
if e.Name != "RoyaltiesTransferred" { | ||
continue | ||
} | ||
event := new(RoyaltiesTransferredEvent) | ||
err := event.FromStackItem(e.Item) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode event from stackitem (event #%d, execution #%d): %w", j, i, err) | ||
} | ||
res = append(res, event) | ||
} | ||
} | ||
return res, nil | ||
} | ||
|
||
// FromStackItem converts a stack item into a RoyaltiesTransferredEvent struct. | ||
func (e *RoyaltiesTransferredEvent) FromStackItem(item *stackitem.Array) error { | ||
if item == nil { | ||
return errors.New("nil item") | ||
} | ||
arr, ok := item.Value().([]stackitem.Item) | ||
if !ok { | ||
return errors.New("not an array") | ||
} | ||
if len(arr) != 5 { | ||
return errors.New("wrong number of event parameters") | ||
} | ||
|
||
b, err := arr[0].TryBytes() | ||
if err != nil { | ||
return fmt.Errorf("invalid RoyaltyToken: %w", err) | ||
} | ||
e.RoyaltyToken, err = util.Uint160DecodeBytesBE(b) | ||
if err != nil { | ||
return fmt.Errorf("failed to decode RoyaltyToken: %w", err) | ||
} | ||
|
||
b, err = arr[1].TryBytes() | ||
if err != nil { | ||
return fmt.Errorf("invalid RoyaltyRecipient: %w", err) | ||
} | ||
e.RoyaltyRecipient, err = util.Uint160DecodeBytesBE(b) | ||
if err != nil { | ||
return fmt.Errorf("failed to decode RoyaltyRecipient: %w", err) | ||
} | ||
|
||
b, err = arr[2].TryBytes() | ||
if err != nil { | ||
return fmt.Errorf("invalid Buyer: %w", err) | ||
} | ||
e.Buyer, err = util.Uint160DecodeBytesBE(b) | ||
if err != nil { | ||
return fmt.Errorf("failed to decode Buyer: %w", err) | ||
} | ||
|
||
e.TokenID, err = arr[3].TryBytes() | ||
if err != nil { | ||
return fmt.Errorf("invalid TokenID: %w", err) | ||
} | ||
|
||
e.Amount, err = arr[4].TryInteger() | ||
if err != nil { | ||
return fmt.Errorf("invalid Amount: %w", err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package nep11 | ||
|
||
import ( | ||
"errors" | ||
"math/big" | ||
"testing" | ||
|
||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" | ||
"github.com/nspcc-dev/neo-go/pkg/util" | ||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// TestRoyaltyReaderRoyaltyInfo tests the RoyaltyInfo method in the RoyaltyReader. | ||
func TestRoyaltyReaderRoyaltyInfo(t *testing.T) { | ||
ta := new(testAct) | ||
rr := NewRoyaltyReader(ta, util.Uint160{1, 2, 3}) | ||
|
||
tokenID := []byte{1, 2, 3} | ||
royaltyToken := util.Uint160{4, 5, 6} | ||
salePrice := big.NewInt(1000) | ||
|
||
tests := []struct { | ||
name string | ||
setupFunc func() | ||
expectErr bool | ||
expectedRI []RoyaltyInfoDetail | ||
}{ | ||
{ | ||
name: "error case", | ||
setupFunc: func() { | ||
ta.err = errors.New("some error") | ||
}, | ||
expectErr: true, | ||
}, | ||
{ | ||
name: "valid response", | ||
setupFunc: func() { | ||
ta.err = nil | ||
recipient := util.Uint160{7, 8, 9} | ||
amount := big.NewInt(100) | ||
ta.res = &result.Invoke{ | ||
State: "HALT", | ||
Stack: []stackitem.Item{stackitem.Make([]stackitem.Item{ | ||
stackitem.Make(recipient.BytesBE()), | ||
stackitem.Make(amount), | ||
})}, | ||
} | ||
}, | ||
expectErr: false, | ||
expectedRI: []RoyaltyInfoDetail{ | ||
{RoyaltyRecipient: util.Uint160{7, 8, 9}, RoyaltyAmount: big.NewInt(100)}, | ||
}, | ||
}, | ||
{ | ||
name: "invalid data response", | ||
setupFunc: func() { | ||
ta.res = &result.Invoke{ | ||
State: "HALT", | ||
Stack: []stackitem.Item{ | ||
stackitem.Make([]stackitem.Item{ | ||
stackitem.Make(util.Uint160{7, 8, 9}.BytesBE()), | ||
}), | ||
}, | ||
} | ||
}, | ||
expectErr: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
tt.setupFunc() | ||
ri, err := rr.RoyaltyInfo(tokenID, royaltyToken, salePrice) | ||
if tt.expectErr { | ||
require.Error(t, err) | ||
} else { | ||
require.NoError(t, err) | ||
require.Equal(t, tt.expectedRI, ri) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package standard | ||
|
||
import ( | ||
"github.com/nspcc-dev/neo-go/pkg/smartcontract" | ||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" | ||
) | ||
|
||
// Nep11WithRoyalty is a NEP-24 Standard for NFT royalties. | ||
var Nep11WithRoyalty = &Standard{ | ||
Base: Nep11Base, | ||
Manifest: manifest.Manifest{ | ||
ABI: manifest.ABI{ | ||
Methods: []manifest.Method{ | ||
{ | ||
Name: "RoyaltyInfo", | ||
Parameters: []manifest.Parameter{ | ||
{Name: "tokenId", Type: smartcontract.ByteArrayType}, | ||
{Name: "royaltyToken", Type: smartcontract.Hash160Type}, | ||
{Name: "salePrice", Type: smartcontract.IntegerType}, | ||
}, | ||
ReturnType: smartcontract.ArrayType, | ||
Safe: true, | ||
}, | ||
}, | ||
Events: []manifest.Event{ | ||
{ | ||
Name: "RoyaltiesTransferred", | ||
Parameters: []manifest.Parameter{ | ||
{Name: "royaltyToken", Type: smartcontract.Hash160Type}, | ||
{Name: "royaltyRecipient", Type: smartcontract.Hash160Type}, | ||
{Name: "buyer", Type: smartcontract.Hash160Type}, | ||
{Name: "tokenId", Type: smartcontract.ByteArrayType}, | ||
{Name: "amount", Type: smartcontract.IntegerType}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters