*: add data to NEP11 Transfer

This commit is contained in:
Anna Shaleva 2021-05-05 13:22:26 +03:00
parent bb039ef035
commit d0c64347ab
18 changed files with 163 additions and 100 deletions

View file

@ -4,11 +4,13 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
"path" "path"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
@ -267,16 +269,51 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) {
// transfer: no id specified // transfer: no id specified
e.In.WriteString(nftOwnerPass + "\r") e.In.WriteString(nftOwnerPass + "\r")
e.RunWithError(t, cmdTransfer...) e.RunWithError(t, cmdTransfer...)
cmdTransfer = append(cmdTransfer, "--id", string(tokenID))
// transfer: good // transfer: good
e.In.WriteString(nftOwnerPass + "\r") e.In.WriteString(nftOwnerPass + "\r")
e.Run(t, cmdTransfer...) e.Run(t, append(cmdTransfer, "--id", string(tokenID))...)
e.checkTxPersisted(t) e.checkTxPersisted(t)
// check balance after transfer // check balance after transfer
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
checkBalanceResult(t, nftOwnerAddr, "1") // tokenID1 checkBalanceResult(t, nftOwnerAddr, "1") // tokenID1
// transfer: good, to NEP11-Payable contract, with data
verifyH := deployVerifyContract(t, e)
cmdTransfer = []string{
"neo-go", "wallet", "nep11", "transfer",
"--rpc-endpoint", "http://" + e.RPC.Addr,
"--wallet", wall,
"--to", verifyH.StringLE(),
"--from", nftOwnerAddr,
"--token", h.StringLE(),
"--id", string(tokenID1),
"string:some_data",
}
e.In.WriteString(nftOwnerPass + "\r")
e.Run(t, cmdTransfer...)
tx, _ := e.checkTxPersisted(t)
// check OnNEP11Payment event
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 2, len(aer[0].Events))
nftOwnerHash, err := address.StringToUint160(nftOwnerAddr)
require.NoError(t, err)
require.Equal(t, state.NotificationEvent{
ScriptHash: verifyH,
Name: "OnNEP11Payment",
Item: stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(nftOwnerHash.BytesBE()),
stackitem.NewBigInteger(big.NewInt(1)),
stackitem.NewByteArray(tokenID1),
stackitem.NewByteArray([]byte("some_data")),
}),
}, aer[0].Events[1])
// check balance after transfer
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
checkBalanceResult(t, nftOwnerAddr, "0")
} }
func deployNFTContract(t *testing.T, e *executor) util.Uint160 { func deployNFTContract(t *testing.T, e *executor) util.Uint160 {

View file

@ -86,7 +86,7 @@ func TestNEP17Balance(t *testing.T) {
} }
e.checkNextLine(t, "^\\s*$") e.checkNextLine(t, "^\\s*$")
addr4, err := address.StringToUint160("NTe3yHH5zsaEGvEHTsFRpCjTef6Aod4yb6") // deployed verify.go contract addr4, err := address.StringToUint160("NaZjSxmRZ4ErG2QEXCQMjjJfvAxMPiutmi") // deployed verify.go contract
require.NoError(t, err) require.NoError(t, err)
e.checkNextLine(t, "^Account "+address.Uint160ToString(addr4)) e.checkNextLine(t, "^Account "+address.Uint160ToString(addr4))
e.checkEOF(t) e.checkEOF(t)

View file

@ -1,6 +1,9 @@
package testdata package testdata
import "github.com/nspcc-dev/neo-go/pkg/interop" import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
)
func Verify() bool { func Verify() bool {
return true return true
@ -8,3 +11,10 @@ func Verify() bool {
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) { func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
} }
// OnNEP11Payment notifies about NEP11 payment. You don't call this method directly,
// instead it's called by NEP11 contract when you transfer funds from your address
// to the address of this NFT contract.
func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data interface{}) {
runtime.Notify("OnNEP11Payment", from, amount, token, data)
}

View file

@ -1 +1,12 @@
name: Test verify name: Test verify
Events:
- name: OnNEP11Payment
parameters:
- name: from
type: Hash160
- name: amount
type: Integer
- name: tokenId
type: ByteArray
- name: data
type: Any

View file

@ -1,84 +1,84 @@
{ {
"version": "3.0", "scrypt" : {
"accounts": [ "r" : 8,
"p" : 8,
"n" : 16384
},
"version" : "3.0",
"accounts" : [
{ {
"address": "NTh9TnZTstvAePEYWDGLLxidBikJE24uTo", "address" : "NTh9TnZTstvAePEYWDGLLxidBikJE24uTo",
"key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label" : "",
"label": "", "lock" : false,
"contract": { "contract" : {
"script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBdHR2qg==", "parameters" : [
"parameters": [
{ {
"name": "parameter0", "type" : "Signature",
"type": "Signature" "name" : "parameter0"
} }
], ],
"deployed": false "script" : "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBdHR2qg==",
"deployed" : false
}, },
"lock": false, "isdefault" : true,
"isdefault": true "key" : "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL"
}, },
{ {
"address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "label" : "",
"key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "address" : "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6",
"label": "", "isdefault" : false,
"contract": { "key" : "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL",
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "contract" : {
"parameters": [ "parameters" : [
{ {
"name": "parameter0", "name" : "parameter0",
"type": "Signature" "type" : "Signature"
}, },
{ {
"name": "parameter1", "type" : "Signature",
"type": "Signature" "name" : "parameter1"
}, },
{ {
"name": "parameter2", "type" : "Signature",
"type": "Signature" "name" : "parameter2"
} }
], ],
"deployed": false "deployed" : false,
"script" : "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl"
}, },
"lock": false, "lock" : false
"isdefault": false
}, },
{ {
"address": "NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF", "address" : "NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF",
"key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL",
"label": "",
"contract": {
"script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEUF7zmyl",
"parameters": [
{
"name": "parameter0",
"type": "Signature"
}
],
"deployed": false
},
"lock": false,
"isdefault": false
},
{
"address" : "NTe3yHH5zsaEGvEHTsFRpCjTef6Aod4yb6",
"key" : "6PYSgdjUPVjo3ZJLg2CsheXnEZzyvUuSm4jCtXP6X7FT82sAQHWt2wpu5A",
"label" : "", "label" : "",
"contract" : { "contract" : {
"script" : "VwEAEdsgQFcAA0A=", "parameters" : [
"deployed" : true, {
"parameters" : [] "name" : "parameter0",
"type" : "Signature"
}
],
"deployed" : false,
"script" : "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEUF7zmyl"
}, },
"lock" : false, "lock" : false,
"isdefault" : false "isdefault" : false,
"key" : "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL"
},
{
"contract" : {
"parameters" : [],
"script" : "VwEAEdsgQFcAA0BXAQR4eXp7VBTAcAwOT25ORVAxMVBheW1lbnRoUEGVAW9hIUA=",
"deployed" : true
},
"lock" : false,
"isdefault" : false,
"key" : "6PYVxVpeNjxGDFTTG61ARGy6mCz6fQsGm9qW2tsFW3ox1zM6KpCoWSE4PB",
"address" : "NaZjSxmRZ4ErG2QEXCQMjjJfvAxMPiutmi",
"label" : "acc"
} }
], ],
"scrypt": { "extra" : {
"n": 16384, "Tokens" : null
"r": 8,
"p": 8
},
"extra": {
"Tokens": null
} }
} }

View file

@ -80,7 +80,7 @@ func newNEP11Commands() []cli.Command {
{ {
Name: "transfer", Name: "transfer",
Usage: "transfer NEP11 tokens", Usage: "transfer NEP11 tokens",
UsageText: "transfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [-- <cosigner1:Scope> [<cosigner2> [...]]]", UsageText: "transfer --wallet <path> --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
Action: transferNEP11, Action: transferNEP11,
Flags: transferFlags, Flags: transferFlags,
Description: `Transfers specified NEP11 token with optional cosigners list attached to Description: `Transfers specified NEP11 token with optional cosigners list attached to
@ -235,7 +235,7 @@ func transferNEP11(ctx *cli.Context) error {
return transferNEP(ctx, manifest.NEP11StandardName) return transferNEP(ctx, manifest.NEP11StandardName)
} }
func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, token, to util.Uint160, tokenID string, amount *big.Int, cosigners []client.SignerAccount) error { func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, token, to util.Uint160, tokenID string, amount *big.Int, data interface{}, cosigners []client.SignerAccount) error {
gas := flags.Fixed8FromContext(ctx, "gas") gas := flags.Fixed8FromContext(ctx, "gas")
var ( var (
@ -247,9 +247,9 @@ func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Ac
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("bad account address: %w", err), 1) return cli.NewExitError(fmt.Errorf("bad account address: %w", err), 1)
} }
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, from, to, amount, tokenID) tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, from, to, amount, tokenID, data)
} else { } else {
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, to, tokenID) tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, to, tokenID, data)
} }
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)

View file

@ -549,13 +549,13 @@ func transferNEP(ctx *cli.Context, standard string) error {
return cli.NewExitError(errors.New("token ID should be specified"), 1) return cli.NewExitError(errors.New("token ID should be specified"), 1)
} }
if amountArg == "" { if amountArg == "" {
return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenID, nil, cosignersAccounts) return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenID, nil, data, cosignersAccounts)
} }
amount, err := fixedn.FromString(amountArg, int(token.Decimals)) amount, err := fixedn.FromString(amountArg, int(token.Decimals))
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1) return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1)
} }
return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenID, amount, cosignersAccounts) return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenID, amount, data, cosignersAccounts)
default: default:
return cli.NewExitError(fmt.Errorf("unsupported token standard %s", standard), 1) return cli.NewExitError(fmt.Errorf("unsupported token standard %s", standard), 1)
} }

View file

@ -181,10 +181,9 @@ func OwnerOf(token []byte) interop.Hash160 {
return getOwnerOf(ctx, token) return getOwnerOf(ctx, token)
} }
// Transfer token from its owner to another user, notice that it only has two // Transfer token from its owner to another user, notice that it only has three
// parameters because token owner can be deduced from token ID itself and RC1 // parameters because token owner can be deduced from token ID itself.
// implementation doesn't yet have 'data' parameter as in NEP-17 Transfer. func Transfer(to interop.Hash160, token []byte, data interface{}) bool {
func Transfer(to interop.Hash160, token []byte) bool {
if len(to) != 20 { if len(to) != 20 {
panic("invalid 'to' address") panic("invalid 'to' address")
} }
@ -212,15 +211,15 @@ func Transfer(to interop.Hash160, token []byte) bool {
setTokensOf(ctx, to, toksTo) setTokensOf(ctx, to, toksTo)
setOwnerOf(ctx, token, to) setOwnerOf(ctx, token, to)
} }
postTransfer(owner, to, token) postTransfer(owner, to, token, data)
return true return true
} }
// postTransfer emits Transfer event and calls onNEP11Payment if needed. // postTransfer emits Transfer event and calls onNEP11Payment if needed.
func postTransfer(from interop.Hash160, to interop.Hash160, token []byte) { func postTransfer(from interop.Hash160, to interop.Hash160, token []byte, data interface{}) {
runtime.Notify("Transfer", from, to, 1, token) runtime.Notify("Transfer", from, to, 1, token)
if management.GetContract(to) != nil { if management.GetContract(to) != nil {
contract.Call(to, "onNEP11Payment", contract.All, from, 1, token) contract.Call(to, "onNEP11Payment", contract.All, from, 1, token, data)
} }
} }
@ -259,7 +258,7 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
total++ total++
storage.Put(ctx, []byte(totalSupplyPrefix), total) storage.Put(ctx, []byte(totalSupplyPrefix), total)
postTransfer(nil, from, []byte(token)) postTransfer(nil, from, []byte(token), nil) // no `data` during minting
} }
// Verify allows owner to manage contract's address, including earned GAS // Verify allows owner to manage contract's address, including earned GAS

View file

@ -108,13 +108,13 @@ func TestOnPayableChecks(t *testing.T) {
t.Run("NEP-11, good", func(t *testing.T) { t.Run("NEP-11, good", func(t *testing.T) {
src := `package payable src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop"
func OnNEP11Payment(from interop.Hash160, amount int, tokenID []byte) {}` func OnNEP11Payment(from interop.Hash160, amount int, tokenID []byte, data interface{}) {}`
require.NoError(t, compileAndCheck(t, src)) require.NoError(t, compileAndCheck(t, src))
}) })
t.Run("NEP-11, bad", func(t *testing.T) { t.Run("NEP-11, bad", func(t *testing.T) {
src := `package payable src := `package payable
import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop"
func OnNEP11Payment(from interop.Hash160, amount int, oldParam string, tokenID []byte) {}` func OnNEP11Payment(from interop.Hash160, amount int, oldParam string, tokenID []byte, data interface{}) {}`
require.Error(t, compileAndCheck(t, src)) require.Error(t, compileAndCheck(t, src))
}) })
t.Run("NEP-17, good", func(t *testing.T) { t.Run("NEP-17, good", func(t *testing.T) {

View file

@ -169,7 +169,7 @@ func TestNativeHelpersCompile(t *testing.T) {
{"properties", []string{`"neo.com"`}}, {"properties", []string{`"neo.com"`}},
{"tokens", nil}, {"tokens", nil},
{"tokensOf", []string{u160}}, {"tokensOf", []string{u160}},
{"transfer", []string{u160, `"neo.com"`}}, {"transfer", []string{u160, `"neo.com"`, "nil"}},
// name service // name service
{"addRoot", []string{`"com"`}}, {"addRoot", []string{`"com"`}},

View file

@ -319,7 +319,7 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
emit.Opcodes(w.BinWriter, opcode.RET) emit.Opcodes(w.BinWriter, opcode.RET)
onNEP11PaymentOff := w.Len() onNEP11PaymentOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash) emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
emit.Int(w.BinWriter, 4) emit.Int(w.BinWriter, 5)
emit.Opcodes(w.BinWriter, opcode.PACK) emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "LostPayment") emit.String(w.BinWriter, "LostPayment")
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
@ -454,6 +454,7 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
manifest.NewParameter("from", smartcontract.Hash160Type), manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType), manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("tokenid", smartcontract.ByteArrayType), manifest.NewParameter("tokenid", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType),
}, },
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,
}, },

View file

@ -107,7 +107,8 @@ func newNonFungible(name string, id int32, symbol string, decimals byte) *nonfun
desc = newDescriptor("transfer", smartcontract.BoolType, desc = newDescriptor("transfer", smartcontract.BoolType,
manifest.NewParameter("to", smartcontract.Hash160Type), manifest.NewParameter("to", smartcontract.Hash160Type),
manifest.NewParameter("tokenId", smartcontract.ByteArrayType)) manifest.NewParameter("tokenId", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType))
md = newMethodAndPrice(n.transfer, 1<<17, callflag.States|callflag.AllowNotify) md = newMethodAndPrice(n.transfer, 1<<17, callflag.States|callflag.AllowNotify)
md.StorageFee = 50 md.StorageFee = 50
n.AddMethod(md, desc) n.AddMethod(md, desc)
@ -267,10 +268,10 @@ func (n *nonfungible) mint(ic *interop.Context, s nftTokenState) {
ts := n.TotalSupply(ic.DAO) ts := n.TotalSupply(ic.DAO)
ts.Add(ts, intOne) ts.Add(ts, intOne)
n.setTotalSupply(ic.DAO, ts) n.setTotalSupply(ic.DAO, ts)
n.postTransfer(ic, nil, &owner, s.ID()) n.postTransfer(ic, nil, &owner, s.ID(), stackitem.Null{})
} }
func (n *nonfungible) postTransfer(ic *interop.Context, from, to *util.Uint160, tokenID []byte) { func (n *nonfungible) postTransfer(ic *interop.Context, from, to *util.Uint160, tokenID []byte, data stackitem.Item) {
ne := state.NotificationEvent{ ne := state.NotificationEvent{
ScriptHash: n.Hash, ScriptHash: n.Hash,
Name: "Transfer", Name: "Transfer",
@ -298,6 +299,7 @@ func (n *nonfungible) postTransfer(ic *interop.Context, from, to *util.Uint160,
fromArg, fromArg,
stackitem.NewBigInteger(intOne), stackitem.NewBigInteger(intOne),
stackitem.NewByteArray(tokenID), stackitem.NewByteArray(tokenID),
data,
} }
if err := contract.CallFromNative(ic, n.Hash, cs, manifest.MethodOnNEP11Payment, args, false); err != nil { if err := contract.CallFromNative(ic, n.Hash, cs, manifest.MethodOnNEP11Payment, args, false); err != nil {
panic(err) panic(err)
@ -332,7 +334,7 @@ func (n *nonfungible) burnByKey(ic *interop.Context, key []byte) {
ts := n.TotalSupply(ic.DAO) ts := n.TotalSupply(ic.DAO)
ts.Sub(ts, intOne) ts.Sub(ts, intOne)
n.setTotalSupply(ic.DAO, ts) n.setTotalSupply(ic.DAO, ts)
n.postTransfer(ic, &owner, nil, id) n.postTransfer(ic, &owner, nil, id, stackitem.Null{})
} }
func (n *nonfungible) transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *nonfungible) transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -373,7 +375,7 @@ func (n *nonfungible) transfer(ic *interop.Context, args []stackitem.Item) stack
acc.Add(tokenID) acc.Add(tokenID)
n.putAccountState(ic.DAO, key, acc) n.putAccountState(ic.DAO, key, acc)
} }
n.postTransfer(ic, &from, &to, tokenID) n.postTransfer(ic, &from, &to, tokenID, args[2])
return stackitem.NewBool(true) return stackitem.NewBool(true)
} }

View file

@ -270,11 +270,11 @@ func TestTransfer(t *testing.T) {
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{}, testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{},
"neo.com", int64(nnsrecords.A), "1.2.3.4") "neo.com", int64(nnsrecords.A), "1.2.3.4")
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer", testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer",
nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists")) nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists"), nil)
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "transfer", testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "transfer",
false, to.Contract.ScriptHash().BytesBE(), []byte("neo.com")) false, to.Contract.ScriptHash().BytesBE(), []byte("neo.com"), nil)
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer", testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer",
true, to.Contract.ScriptHash().BytesBE(), []byte("neo.com")) true, to.Contract.ScriptHash().BytesBE(), []byte("neo.com"), nil)
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "totalSupply", 1) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "totalSupply", 1)
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "ownerOf", testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "ownerOf",
to.Contract.ScriptHash().BytesBE(), []byte("neo.com")) to.Contract.ScriptHash().BytesBE(), []byte("neo.com"))
@ -282,9 +282,9 @@ func TestTransfer(t *testing.T) {
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs2)) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs2))
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, to, "transfer", testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, to, "transfer",
nil, cs2.Hash.BytesBE(), []byte("neo.com")) nil, cs2.Hash.BytesBE(), []byte("neo.com"), nil)
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, to, "transfer", testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, to, "transfer",
true, cs.Hash.BytesBE(), []byte("neo.com")) true, cs.Hash.BytesBE(), []byte("neo.com"), nil)
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "totalSupply", 1) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "totalSupply", 1)
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "ownerOf", testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "ownerOf",
cs.Hash.BytesBE(), []byte("neo.com")) cs.Hash.BytesBE(), []byte("neo.com"))

View file

@ -67,9 +67,9 @@ func TokensOf(addr interop.Hash160) iterator.Iterator {
} }
// Transfer represents `transfer` method of NameService native contract. // Transfer represents `transfer` method of NameService native contract.
func Transfer(to interop.Hash160, tokenID string) bool { func Transfer(to interop.Hash160, tokenID string, data interface{}) bool {
return contract.Call(interop.Hash160(Hash), "transfer", return contract.Call(interop.Hash160(Hash), "transfer",
contract.ReadStates|contract.States|contract.AllowNotify, to, tokenID).(bool) contract.ReadStates|contract.States|contract.AllowNotify, to, tokenID, data).(bool)
} }
// AddRoot represents `addRoot` method of NameService native contract. // AddRoot represents `addRoot` method of NameService native contract.

View file

@ -45,11 +45,11 @@ func (c *Client) NEP11TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
// on a given token to move the whole NEP11 token with the specified token ID to // on a given token to move the whole NEP11 token with the specified token ID to
// given account and sends it to the network returning just a hash of it. // given account and sends it to the network returning just a hash of it.
func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160, func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
tokenHash util.Uint160, tokenID string, gas int64, cosigners []SignerAccount) (util.Uint256, error) { tokenHash util.Uint160, tokenID string, data interface{}, gas int64, cosigners []SignerAccount) (util.Uint256, error) {
if !c.initDone { if !c.initDone {
return util.Uint256{}, errNetworkNotInitialized return util.Uint256{}, errNetworkNotInitialized
} }
tx, err := c.CreateNEP11TransferTx(acc, tokenHash, gas, cosigners, to, tokenID) tx, err := c.CreateNEP11TransferTx(acc, tokenHash, gas, cosigners, to, tokenID, data)
if err != nil { if err != nil {
return util.Uint256{}, err return util.Uint256{}, err
} }
@ -62,8 +62,8 @@ func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
// of) NEP11 token with the specified token ID to given account and returns it. // of) NEP11 token with the specified token ID to given account and returns it.
// The returned transaction is not signed. CreateNEP11TransferTx is also a // The returned transaction is not signed. CreateNEP11TransferTx is also a
// helper for TransferNEP11 and TransferNEP11D. // helper for TransferNEP11 and TransferNEP11D.
// `args` for TransferNEP11: to util.Uint160, tokenID string; // `args` for TransferNEP11: to util.Uint160, tokenID string, data interface{};
// `args` for TransferNEP11D: from, to util.Uint160, amount int64, tokenID string. // `args` for TransferNEP11D: from, to util.Uint160, amount int64, tokenID string, data interface{}.
func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint160, func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint160,
gas int64, cosigners []SignerAccount, args ...interface{}) (*transaction.Transaction, error) { gas int64, cosigners []SignerAccount, args ...interface{}) (*transaction.Transaction, error) {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
@ -143,7 +143,7 @@ func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID string) (util.Ui
// (in FixedN format using contract's number of decimals) to given account and // (in FixedN format using contract's number of decimals) to given account and
// sends it to the network returning just a hash of it. // sends it to the network returning just a hash of it.
func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160, func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
tokenHash util.Uint160, amount int64, tokenID string, gas int64, cosigners []SignerAccount) (util.Uint256, error) { tokenHash util.Uint160, amount int64, tokenID string, data interface{}, gas int64, cosigners []SignerAccount) (util.Uint256, error) {
if !c.initDone { if !c.initDone {
return util.Uint256{}, errNetworkNotInitialized return util.Uint256{}, errNetworkNotInitialized
} }
@ -151,7 +151,7 @@ func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
if err != nil { if err != nil {
return util.Uint256{}, fmt.Errorf("bad account address: %w", err) return util.Uint256{}, fmt.Errorf("bad account address: %w", err)
} }
tx, err := c.CreateNEP11TransferTx(acc, tokenHash, gas, cosigners, from, to, amount, tokenID) tx, err := c.CreateNEP11TransferTx(acc, tokenHash, gas, cosigners, from, to, amount, tokenID, data)
if err != nil { if err != nil {
return util.Uint256{}, err return util.Uint256{}, err
} }

View file

@ -834,7 +834,7 @@ func TestClient_NEP11(t *testing.T) {
require.EqualValues(t, expected, p) require.EqualValues(t, expected, p)
}) })
t.Run("Transfer", func(t *testing.T) { t.Run("Transfer", func(t *testing.T) {
_, err := c.TransferNEP11(wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)), testchain.PrivateKeyByID(1).GetScriptHash(), h, "neo.com", 0, nil) _, err := c.TransferNEP11(wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)), testchain.PrivateKeyByID(1).GetScriptHash(), h, "neo.com", nil, 0, nil)
require.NoError(t, err) require.NoError(t, err)
}) })
} }

View file

@ -31,6 +31,7 @@ var nep11Base = &Standard{
Parameters: []manifest.Parameter{ Parameters: []manifest.Parameter{
{Name: "to", Type: smartcontract.Hash160Type}, {Name: "to", Type: smartcontract.Hash160Type},
{Name: "tokenId", Type: smartcontract.ByteArrayType}, {Name: "tokenId", Type: smartcontract.ByteArrayType},
{Name: "data", Type: smartcontract.AnyType},
}, },
ReturnType: smartcontract.BoolType, ReturnType: smartcontract.BoolType,
}, },
@ -112,6 +113,7 @@ var nep11Divisible = &Standard{
{Name: "to", Type: smartcontract.Hash160Type}, {Name: "to", Type: smartcontract.Hash160Type},
{Name: "amount", Type: smartcontract.IntegerType}, {Name: "amount", Type: smartcontract.IntegerType},
{Name: "tokenId", Type: smartcontract.ByteArrayType}, {Name: "tokenId", Type: smartcontract.ByteArrayType},
{Name: "data", Type: smartcontract.AnyType},
}, },
ReturnType: smartcontract.BoolType, ReturnType: smartcontract.BoolType,
}, },

View file

@ -14,6 +14,7 @@ var nep11payable = &Standard{
{Name: "from", Type: smartcontract.Hash160Type}, {Name: "from", Type: smartcontract.Hash160Type},
{Name: "amount", Type: smartcontract.IntegerType}, {Name: "amount", Type: smartcontract.IntegerType},
{Name: "tokenid", Type: smartcontract.ByteArrayType}, {Name: "tokenid", Type: smartcontract.ByteArrayType},
{Name: "data", Type: smartcontract.AnyType},
}, },
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,
}}, }},