mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-26 19:42:23 +00:00
nep17: provide out of the box multitransfer capability
It can't replicate the old multitransfer methods in ability to transfer multiple tokens, but it at the same time can do multiple senders.
This commit is contained in:
parent
823c4b38fc
commit
c034f94a94
4 changed files with 131 additions and 40 deletions
|
@ -87,6 +87,10 @@ func (c *Client) CreateNEP17TransferTx(acc *wallet.Account, to util.Uint160,
|
|||
// NEP-17 transfers from a single sender to multiple recipients with the given
|
||||
// data and cosigners. The transaction sender is included with the CalledByEntry
|
||||
// scope by default.
|
||||
//
|
||||
// Deprecated: please use nep17 package (when transferring the same token) or
|
||||
// [smartcontract.Builder] (when transferring multiple tokens), this method will
|
||||
// be removed in future versions.
|
||||
func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64,
|
||||
recipients []TransferTarget, cosigners []SignerAccount) (*transaction.Transaction, error) {
|
||||
from, err := address.StringToUint160(acc.Address)
|
||||
|
@ -171,6 +175,10 @@ func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util.
|
|||
}
|
||||
|
||||
// MultiTransferNEP17 is similar to TransferNEP17, buf allows to have multiple recipients.
|
||||
//
|
||||
// Deprecated: please use nep17 package (when transferring the same token) or
|
||||
// [smartcontract.Builder] (when transferring multiple tokens), this method will
|
||||
// be removed in future versions.
|
||||
func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients []TransferTarget, cosigners []SignerAccount) (util.Uint256, error) {
|
||||
tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients, cosigners)
|
||||
if err != nil {
|
||||
|
|
|
@ -7,6 +7,7 @@ various methods to perform the only NEP-17 state-changing call, Transfer.
|
|||
package nep17
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
|
@ -53,6 +54,14 @@ type TransferEvent struct {
|
|||
Amount *big.Int
|
||||
}
|
||||
|
||||
// TransferParameters is a set of parameters for `transfer` method.
|
||||
type TransferParameters struct {
|
||||
From util.Uint160
|
||||
To util.Uint160
|
||||
Amount *big.Int
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// NewReader creates an instance of TokenReader for contract with the given hash
|
||||
// using the given Invoker.
|
||||
func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
|
||||
|
@ -75,11 +84,7 @@ func (t *TokenReader) BalanceOf(account util.Uint160) (*big.Int, error) {
|
|||
// transaction if it's not true. The returned values are transaction hash, its
|
||||
// ValidUntilBlock value and an error if any.
|
||||
func (t *Token) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error) {
|
||||
script, err := t.transferScript(from, to, amount, data)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return t.actor.SendRun(script)
|
||||
return t.MultiTransfer([]TransferParameters{{from, to, amount, data}})
|
||||
}
|
||||
|
||||
// TransferTransaction creates a transaction that performs a `transfer` method
|
||||
|
@ -87,11 +92,7 @@ func (t *Token) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, da
|
|||
// transaction if it's not true. This transaction is signed, but not sent to the
|
||||
// network, instead it's returned to the caller.
|
||||
func (t *Token) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := t.transferScript(from, to, amount, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.actor.MakeRun(script)
|
||||
return t.MultiTransferTransaction([]TransferParameters{{from, to, amount, data}})
|
||||
}
|
||||
|
||||
// TransferUnsigned creates a transaction that performs a `transfer` method
|
||||
|
@ -99,13 +100,51 @@ func (t *Token) TransferTransaction(from util.Uint160, to util.Uint160, amount *
|
|||
// transaction if it's not true. This transaction is not signed and just returned
|
||||
// to the caller.
|
||||
func (t *Token) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := t.transferScript(from, to, amount, data)
|
||||
return t.MultiTransferUnsigned([]TransferParameters{{from, to, amount, data}})
|
||||
}
|
||||
|
||||
func (t *Token) multiTransferScript(params []TransferParameters) ([]byte, error) {
|
||||
if len(params) == 0 {
|
||||
return nil, errors.New("at least one transfer parameter required")
|
||||
}
|
||||
b := smartcontract.NewBuilder()
|
||||
for i := range params {
|
||||
b.InvokeWithAssert(t.hash, "transfer", params[i].From,
|
||||
params[i].To, params[i].Amount, params[i].Data)
|
||||
}
|
||||
return b.Script()
|
||||
}
|
||||
|
||||
// MultiTransfer is not a real NEP-17 method, but rather a convenient way to
|
||||
// perform multiple transfers (usually from a single account) in one transaction.
|
||||
// It accepts a set of parameters, creates a script that calls `transfer` as
|
||||
// many times as needed (with ASSERTs added, so if any of these transfers fail
|
||||
// whole transaction (with all transfers) fails). The values returned are the
|
||||
// same as in Transfer.
|
||||
func (t *Token) MultiTransfer(params []TransferParameters) (util.Uint256, uint32, error) {
|
||||
script, err := t.multiTransferScript(params)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return t.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// MultiTransferTransaction is similar to MultiTransfer, but returns the same values
|
||||
// as TransferTransaction (signed transaction that is not yet sent).
|
||||
func (t *Token) MultiTransferTransaction(params []TransferParameters) (*transaction.Transaction, error) {
|
||||
script, err := t.multiTransferScript(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// MultiTransferUnsigned is similar to MultiTransfer, but returns the same values
|
||||
// as TransferUnsigned (not yet signed transaction).
|
||||
func (t *Token) MultiTransferUnsigned(params []TransferParameters) (*transaction.Transaction, error) {
|
||||
script, err := t.multiTransferScript(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
func (t *Token) transferScript(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(t.hash, "transfer", from, to, amount, data)
|
||||
}
|
||||
|
|
|
@ -66,19 +66,32 @@ func TestTokenTransfer(t *testing.T) {
|
|||
ta := new(testAct)
|
||||
tok := New(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err := tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
for name, fun := range map[string]func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error){
|
||||
"Tranfer": tok.Transfer,
|
||||
"MultiTransfer": func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error) {
|
||||
return tok.MultiTransfer([]TransferParameters{{from, to, amount, data}, {from, to, amount, data}})
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, _, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
h, vub, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
_, _, err := tok.MultiTransfer(nil)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
h, vub, err := tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
||||
_, _, err = tok.MultiTransfer([]TransferParameters{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -86,21 +99,37 @@ func TestTokenTransferTransaction(t *testing.T) {
|
|||
ta := new(testAct)
|
||||
tok := New(ta, util.Uint160{1, 2, 3})
|
||||
|
||||
for _, fun := range []func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error){
|
||||
tok.TransferTransaction,
|
||||
tok.TransferUnsigned,
|
||||
for name, fun := range map[string]func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error){
|
||||
"TransferTransaction": tok.TransferTransaction,
|
||||
"TransferUnsigned": tok.TransferUnsigned,
|
||||
"MultiTransferTransaction": func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
|
||||
return tok.MultiTransferTransaction([]TransferParameters{{from, to, amount, data}, {from, to, amount, data}})
|
||||
},
|
||||
"MultiTransferUnsigned": func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
|
||||
return tok.MultiTransferUnsigned([]TransferParameters{{from, to, amount, data}, {from, to, amount, data}})
|
||||
},
|
||||
} {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
require.Error(t, err)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ta.err = errors.New("")
|
||||
_, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
ta.err = nil
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
||||
require.Error(t, err)
|
||||
_, err = fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
_, err := tok.MultiTransferTransaction(nil)
|
||||
require.Error(t, err)
|
||||
_, err = tok.MultiTransferTransaction([]TransferParameters{})
|
||||
require.Error(t, err)
|
||||
_, err = tok.MultiTransferUnsigned(nil)
|
||||
require.Error(t, err)
|
||||
_, err = tok.MultiTransferUnsigned([]TransferParameters{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
|
|
@ -1071,6 +1071,21 @@ func TestCreateNEP17TransferTx(t *testing.T) {
|
|||
ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
|
||||
require.NoError(t, ic.VM.Run())
|
||||
})
|
||||
t.Run("default scope, multitransfer", func(t *testing.T) {
|
||||
act, err := actor.NewSimple(c, acc)
|
||||
require.NoError(t, err)
|
||||
gazprom := gas.New(act)
|
||||
tx, err := gazprom.MultiTransferTransaction([]nep17.TransferParameters{
|
||||
{From: addr, To: util.Uint160{3, 2, 1}, Amount: big.NewInt(1000), Data: nil},
|
||||
{From: addr, To: util.Uint160{1, 2, 3}, Amount: big.NewInt(1000), Data: nil},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, chain.VerifyTx(tx))
|
||||
ic := chain.GetTestVM(trigger.Application, tx, nil)
|
||||
ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
|
||||
require.NoError(t, ic.VM.Run())
|
||||
require.Equal(t, 2, len(ic.Notifications))
|
||||
})
|
||||
t.Run("none scope", func(t *testing.T) {
|
||||
act, err := actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
|
|
Loading…
Reference in a new issue