forked from TrueCloudLab/neoneo-go
Merge pull request #2653 from nspcc-dev/nep17-multi
NEP-17 multitransfers
This commit is contained in:
commit
d2cf5b5532
11 changed files with 261 additions and 190 deletions
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"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/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
@ -197,17 +197,32 @@ func ParseParams(args []string, calledFromMain bool) (int, []smartcontract.Param
|
||||||
|
|
||||||
// GetSignersAccounts returns the list of signers combined with the corresponding
|
// GetSignersAccounts returns the list of signers combined with the corresponding
|
||||||
// accounts from the provided wallet.
|
// accounts from the provided wallet.
|
||||||
func GetSignersAccounts(wall *wallet.Wallet, signers []transaction.Signer) ([]rpcclient.SignerAccount, error) {
|
func GetSignersAccounts(senderAcc *wallet.Account, wall *wallet.Wallet, signers []transaction.Signer, accScope transaction.WitnessScope) ([]actor.SignerAccount, error) {
|
||||||
signersAccounts := make([]rpcclient.SignerAccount, len(signers))
|
signersAccounts := make([]actor.SignerAccount, 0, len(signers)+1)
|
||||||
for i := range signers {
|
sender, err := address.StringToUint160(senderAcc.Address)
|
||||||
signerAcc := wall.GetAccount(signers[i].Account)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signersAccounts = append(signersAccounts, actor.SignerAccount{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: sender,
|
||||||
|
Scopes: accScope,
|
||||||
|
},
|
||||||
|
Account: senderAcc,
|
||||||
|
})
|
||||||
|
for i, s := range signers {
|
||||||
|
if s.Account == sender {
|
||||||
|
signersAccounts[0].Signer = s
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
signerAcc := wall.GetAccount(s.Account)
|
||||||
if signerAcc == nil {
|
if signerAcc == nil {
|
||||||
return nil, fmt.Errorf("no account was found in the wallet for signer #%d (%s)", i, address.Uint160ToString(signers[i].Account))
|
return nil, fmt.Errorf("no account was found in the wallet for signer #%d (%s)", i, address.Uint160ToString(s.Account))
|
||||||
}
|
}
|
||||||
signersAccounts[i] = rpcclient.SignerAccount{
|
signersAccounts = append(signersAccounts, actor.SignerAccount{
|
||||||
Signer: signers[i],
|
Signer: s,
|
||||||
Account: signerAcc,
|
Account: signerAcc,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
return signersAccounts, nil
|
return signersAccounts, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"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/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
@ -659,25 +658,22 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
||||||
|
|
||||||
func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []smartcontract.Parameter, cosigners []transaction.Signer) (util.Uint160, error) {
|
func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []smartcontract.Parameter, cosigners []transaction.Signer) (util.Uint160, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
gas, sysgas fixedn.Fixed8
|
gas, sysgas fixedn.Fixed8
|
||||||
cosignersAccounts []rpcclient.SignerAccount
|
signersAccounts []actor.SignerAccount
|
||||||
resp *result.Invoke
|
resp *result.Invoke
|
||||||
sender util.Uint160
|
sender util.Uint160
|
||||||
signAndPush = acc != nil
|
signAndPush = acc != nil
|
||||||
act *actor.Actor
|
act *actor.Actor
|
||||||
)
|
)
|
||||||
if signAndPush {
|
if signAndPush {
|
||||||
gas = flags.Fixed8FromContext(ctx, "gas")
|
gas = flags.Fixed8FromContext(ctx, "gas")
|
||||||
sysgas = flags.Fixed8FromContext(ctx, "sysgas")
|
sysgas = flags.Fixed8FromContext(ctx, "sysgas")
|
||||||
sender, err = address.StringToUint160(acc.Address)
|
signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sender, err
|
return sender, cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||||
}
|
|
||||||
cosignersAccounts, err = cmdargs.GetSignersAccounts(wall, cosigners)
|
|
||||||
if err != nil {
|
|
||||||
return sender, cli.NewExitError(fmt.Errorf("failed to calculate network fee: %w", err), 1)
|
|
||||||
}
|
}
|
||||||
|
sender = signersAccounts[0].Signer.Account
|
||||||
}
|
}
|
||||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -687,26 +683,7 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
|
||||||
return sender, err
|
return sender, err
|
||||||
}
|
}
|
||||||
if signAndPush {
|
if signAndPush {
|
||||||
// This will eventually be handled in cmdargs.GetSignersAccounts.
|
act, err = actor.New(c, signersAccounts)
|
||||||
asa := make([]actor.SignerAccount, 0, len(cosigners)+1)
|
|
||||||
asa = append(asa, actor.SignerAccount{
|
|
||||||
Signer: transaction.Signer{
|
|
||||||
Account: sender,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
},
|
|
||||||
Account: acc,
|
|
||||||
})
|
|
||||||
for _, c := range cosignersAccounts {
|
|
||||||
if c.Signer.Account == sender {
|
|
||||||
asa[0].Signer = c.Signer
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
asa = append(asa, actor.SignerAccount{
|
|
||||||
Signer: c.Signer,
|
|
||||||
Account: c.Account,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
act, err = actor.New(c, asa)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sender, cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1)
|
return sender, cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,18 +9,13 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
|
||||||
"github.com/nspcc-dev/neo-go/cli/options"
|
"github.com/nspcc-dev/neo-go/cli/options"
|
||||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
||||||
"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/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||||
"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/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
@ -262,61 +257,6 @@ func transferNEP11(ctx *cli.Context) error {
|
||||||
return transferNEP(ctx, manifest.NEP11StandardName)
|
return transferNEP(ctx, manifest.NEP11StandardName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func signAndSendNEP11Transfer(ctx *cli.Context, c *rpcclient.Client, acc *wallet.Account, token, to util.Uint160, tokenID []byte, amount *big.Int, data interface{}, cosigners []rpcclient.SignerAccount) error {
|
|
||||||
gas := flags.Fixed8FromContext(ctx, "gas")
|
|
||||||
sysgas := flags.Fixed8FromContext(ctx, "sysgas")
|
|
||||||
|
|
||||||
var (
|
|
||||||
tx *transaction.Transaction
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if amount != nil {
|
|
||||||
var from util.Uint160
|
|
||||||
|
|
||||||
from, err = address.StringToUint160(acc.Address)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("bad account address: %w", err), 1)
|
|
||||||
}
|
|
||||||
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, from, to, amount, tokenID, data) //nolint:staticcheck // SA1019: c.CreateNEP11TransferTx is deprecated
|
|
||||||
} else {
|
|
||||||
tx, err = c.CreateNEP11TransferTx(acc, token, int64(gas), cosigners, to, tokenID, data) //nolint:staticcheck // SA1019: c.CreateNEP11TransferTx is deprecated
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
tx.SystemFee += int64(sysgas)
|
|
||||||
|
|
||||||
if outFile := ctx.String("out"); outFile != "" {
|
|
||||||
ver, err := c.GetVersion()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("RPC failure: %w", err), 1)
|
|
||||||
}
|
|
||||||
// Make a long-lived transaction, it's to be signed manually.
|
|
||||||
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
|
|
||||||
m, err := c.GetNetwork()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("failed to save tx: %w", err), 1)
|
|
||||||
}
|
|
||||||
if err := paramcontext.InitAndSave(m, tx, acc, outFile); err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !ctx.Bool("force") {
|
|
||||||
err := input.ConfirmTx(ctx.App.Writer, tx)
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err := c.SignAndPushTx(tx, acc, cosigners) //nolint:staticcheck // SA1019: c.SignAndPushTx is deprecated
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printNEP11NDOwner(ctx *cli.Context) error {
|
func printNEP11NDOwner(ctx *cli.Context) error {
|
||||||
return printNEP11Owner(ctx, false)
|
return printNEP11Owner(ctx, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,25 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/cli/options"
|
"github.com/nspcc-dev/neo-go/cli/options"
|
||||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"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/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"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/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
@ -546,12 +552,20 @@ func multiTransferNEP17(ctx *cli.Context) error {
|
||||||
if extErr != nil {
|
if extErr != nil {
|
||||||
return extErr
|
return extErr
|
||||||
}
|
}
|
||||||
cosignersAccounts, err := cmdargs.GetSignersAccounts(wall, cosigners)
|
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("failed to create NEP-17 multitransfer transaction: %w", err), 1)
|
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||||
|
}
|
||||||
|
act, err := actor.New(c, signersAccounts)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return signAndSendNEP17Transfer(ctx, c, acc, recipients, cosignersAccounts)
|
tx, err := makeMultiTransferNEP17(act, recipients)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1)
|
||||||
|
}
|
||||||
|
return signAndSendSomeTransaction(ctx, act, acc, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferNEP17(ctx *cli.Context) error {
|
func transferNEP17(ctx *cli.Context) error {
|
||||||
|
@ -559,6 +573,8 @@ func transferNEP17(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferNEP(ctx *cli.Context, standard string) error {
|
func transferNEP(ctx *cli.Context, standard string) error {
|
||||||
|
var tx *transaction.Transaction
|
||||||
|
|
||||||
wall, pass, err := readWallet(ctx)
|
wall, pass, err := readWallet(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
|
@ -604,81 +620,94 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
||||||
if extErr != nil {
|
if extErr != nil {
|
||||||
return extErr
|
return extErr
|
||||||
}
|
}
|
||||||
cosignersAccounts, err := cmdargs.GetSignersAccounts(wall, cosigners)
|
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("failed to create NEP-17 transfer transaction: %w", err), 1)
|
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||||
|
}
|
||||||
|
act, err := actor.New(c, signersAccounts)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
amountArg := ctx.String("amount")
|
amountArg := ctx.String("amount")
|
||||||
|
amount, err := fixedn.FromString(amountArg, int(token.Decimals))
|
||||||
|
// It's OK for NEP-11 transfer to not have amount set.
|
||||||
|
if err != nil && (standard == manifest.NEP17StandardName || amountArg != "") {
|
||||||
|
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1)
|
||||||
|
}
|
||||||
switch standard {
|
switch standard {
|
||||||
case manifest.NEP17StandardName:
|
case manifest.NEP17StandardName:
|
||||||
amount, err := fixedn.FromString(amountArg, int(token.Decimals))
|
n17 := nep17.New(act, token.Hash)
|
||||||
if err != nil {
|
tx, err = n17.TransferUnsigned(act.Sender(), to, amount, data)
|
||||||
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1)
|
|
||||||
}
|
|
||||||
return signAndSendNEP17Transfer(ctx, c, acc, []rpcclient.TransferTarget{{
|
|
||||||
Token: token.Hash,
|
|
||||||
Address: to,
|
|
||||||
Amount: amount.Int64(),
|
|
||||||
Data: data,
|
|
||||||
}}, cosignersAccounts)
|
|
||||||
case manifest.NEP11StandardName:
|
case manifest.NEP11StandardName:
|
||||||
tokenID := ctx.String("id")
|
tokenID := ctx.String("id")
|
||||||
if tokenID == "" {
|
if tokenID == "" {
|
||||||
return cli.NewExitError(errors.New("token ID should be specified"), 1)
|
return cli.NewExitError(errors.New("token ID should be specified"), 1)
|
||||||
}
|
}
|
||||||
tokenIDBytes, err := hex.DecodeString(tokenID)
|
tokenIDBytes, terr := hex.DecodeString(tokenID)
|
||||||
if err != nil {
|
if terr != nil {
|
||||||
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", err), 1)
|
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", terr), 1)
|
||||||
}
|
}
|
||||||
if amountArg == "" {
|
if amountArg == "" {
|
||||||
return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenIDBytes, nil, data, cosignersAccounts)
|
n11 := nep11.NewNonDivisible(act, token.Hash)
|
||||||
|
tx, err = n11.TransferUnsigned(to, tokenIDBytes, data)
|
||||||
|
} else {
|
||||||
|
n11 := nep11.NewDivisible(act, token.Hash)
|
||||||
|
tx, err = n11.TransferDUnsigned(act.Sender(), to, amount, tokenIDBytes, data)
|
||||||
}
|
}
|
||||||
amount, err := fixedn.FromString(amountArg, int(token.Decimals))
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1)
|
|
||||||
}
|
|
||||||
return signAndSendNEP11Transfer(ctx, c, acc, token.Hash, to, tokenIDBytes, 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)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signAndSendSomeTransaction(ctx, act, acc, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func signAndSendNEP17Transfer(ctx *cli.Context, c *rpcclient.Client, acc *wallet.Account, recipients []rpcclient.TransferTarget, cosigners []rpcclient.SignerAccount) error {
|
func makeMultiTransferNEP17(act *actor.Actor, recipients []rpcclient.TransferTarget) (*transaction.Transaction, error) {
|
||||||
gas := flags.Fixed8FromContext(ctx, "gas")
|
scr := smartcontract.NewBuilder()
|
||||||
sysgas := flags.Fixed8FromContext(ctx, "sysgas")
|
for i := range recipients {
|
||||||
|
scr.InvokeWithAssert(recipients[i].Token, "transfer", act.Sender(),
|
||||||
tx, err := c.CreateNEP17MultiTransferTx(acc, int64(gas), recipients, cosigners)
|
recipients[i].Address, recipients[i].Amount, recipients[i].Data)
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
}
|
||||||
tx.SystemFee += int64(sysgas)
|
script, err := scr.Script()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return act.MakeUnsignedRun(script, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func signAndSendSomeTransaction(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *transaction.Transaction) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
gas = flags.Fixed8FromContext(ctx, "gas")
|
||||||
|
sysgas = flags.Fixed8FromContext(ctx, "sysgas")
|
||||||
|
)
|
||||||
|
|
||||||
|
tx.SystemFee += int64(sysgas)
|
||||||
|
tx.NetworkFee += int64(gas)
|
||||||
|
|
||||||
|
ver := act.GetVersion()
|
||||||
if outFile := ctx.String("out"); outFile != "" {
|
if outFile := ctx.String("out"); outFile != "" {
|
||||||
ver, err := c.GetVersion()
|
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("RPC failure: %w", err), 1)
|
|
||||||
}
|
|
||||||
// Make a long-lived transaction, it's to be signed manually.
|
// Make a long-lived transaction, it's to be signed manually.
|
||||||
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
|
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
|
||||||
m, err := c.GetNetwork()
|
err = paramcontext.InitAndSave(ver.Protocol.Network, tx, acc, outFile)
|
||||||
if err != nil {
|
|
||||||
return cli.NewExitError(fmt.Errorf("failed to save tx: %w", err), 1)
|
|
||||||
}
|
|
||||||
if err := paramcontext.InitAndSave(m, tx, acc, outFile); err != nil {
|
|
||||||
return cli.NewExitError(err, 1)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if !ctx.Bool("force") {
|
if !ctx.Bool("force") {
|
||||||
|
promptTime := time.Now()
|
||||||
err := input.ConfirmTx(ctx.App.Writer, tx)
|
err := input.ConfirmTx(ctx.App.Writer, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
waitTime := time.Since(promptTime)
|
||||||
|
// Compensate for confirmation waiting.
|
||||||
|
tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1
|
||||||
}
|
}
|
||||||
_, err := c.SignAndPushTx(tx, acc, cosigners) //nolint:staticcheck // SA1019: c.SignAndPushTx is deprecated
|
_, _, err = act.SignAndSend(tx)
|
||||||
if err != nil {
|
}
|
||||||
return cli.NewExitError(err, 1)
|
if err != nil {
|
||||||
}
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
||||||
|
|
|
@ -38,6 +38,8 @@ func TestDecimalFromStringBad(t *testing.T) {
|
||||||
s string
|
s string
|
||||||
prec int
|
prec int
|
||||||
}{
|
}{
|
||||||
|
{"", 0},
|
||||||
|
{"", 1},
|
||||||
{"12A", 1},
|
{"12A", 1},
|
||||||
{"12.345", 2},
|
{"12.345", 2},
|
||||||
{"12.3A", 2},
|
{"12.3A", 2},
|
||||||
|
|
|
@ -202,3 +202,9 @@ func (a *Actor) SendTunedRun(script []byte, attrs []transaction.Attribute, txHoo
|
||||||
func (a *Actor) SendUncheckedRun(script []byte, sysfee int64, attrs []transaction.Attribute, txHook TransactionModifier) (util.Uint256, uint32, error) {
|
func (a *Actor) SendUncheckedRun(script []byte, sysfee int64, attrs []transaction.Attribute, txHook TransactionModifier) (util.Uint256, uint32, error) {
|
||||||
return a.sendWrapper(a.MakeUncheckedRun(script, sysfee, attrs, txHook))
|
return a.sendWrapper(a.MakeUncheckedRun(script, sysfee, attrs, txHook))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sender return the sender address that will be used in transactions created
|
||||||
|
// by Actor.
|
||||||
|
func (a *Actor) Sender() util.Uint160 {
|
||||||
|
return a.txSigners[0].Account
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -259,3 +260,13 @@ func TestSenders(t *testing.T) {
|
||||||
require.Equal(t, client.hash, h)
|
require.Equal(t, client.hash, h)
|
||||||
require.Equal(t, uint32(8), vub)
|
require.Equal(t, uint32(8), vub)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSender(t *testing.T) {
|
||||||
|
client, acc := testRPCAndAccount(t)
|
||||||
|
a, err := NewSimple(client, acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addr, err := address.StringToUint160(acc.Address)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, addr, a.Sender())
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
// NEP-17 transfers from a single sender to multiple recipients with the given
|
||||||
// data and cosigners. The transaction sender is included with the CalledByEntry
|
// data and cosigners. The transaction sender is included with the CalledByEntry
|
||||||
// scope by default.
|
// 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,
|
func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64,
|
||||||
recipients []TransferTarget, cosigners []SignerAccount) (*transaction.Transaction, error) {
|
recipients []TransferTarget, cosigners []SignerAccount) (*transaction.Transaction, error) {
|
||||||
from, err := address.StringToUint160(acc.Address)
|
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.
|
// 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) {
|
func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients []TransferTarget, cosigners []SignerAccount) (util.Uint256, error) {
|
||||||
tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients, cosigners)
|
tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients, cosigners)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,6 +7,7 @@ various methods to perform the only NEP-17 state-changing call, Transfer.
|
||||||
package nep17
|
package nep17
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
@ -53,6 +54,14 @@ type TransferEvent struct {
|
||||||
Amount *big.Int
|
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
|
// NewReader creates an instance of TokenReader for contract with the given hash
|
||||||
// using the given Invoker.
|
// using the given Invoker.
|
||||||
func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
|
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
|
// transaction if it's not true. The returned values are transaction hash, its
|
||||||
// ValidUntilBlock value and an error if any.
|
// 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) {
|
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)
|
return t.MultiTransfer([]TransferParameters{{from, to, amount, data}})
|
||||||
if err != nil {
|
|
||||||
return util.Uint256{}, 0, err
|
|
||||||
}
|
|
||||||
return t.actor.SendRun(script)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransferTransaction creates a transaction that performs a `transfer` method
|
// 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
|
// transaction if it's not true. This transaction is signed, but not sent to the
|
||||||
// network, instead it's returned to the caller.
|
// 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) {
|
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)
|
return t.MultiTransferTransaction([]TransferParameters{{from, to, amount, data}})
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return t.actor.MakeRun(script)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransferUnsigned creates a transaction that performs a `transfer` method
|
// 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
|
// transaction if it's not true. This transaction is not signed and just returned
|
||||||
// to the caller.
|
// to the caller.
|
||||||
func (t *Token) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return t.actor.MakeUnsignedRun(script, nil)
|
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)
|
ta := new(testAct)
|
||||||
tok := New(ta, util.Uint160{1, 2, 3})
|
tok := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
ta.err = errors.New("")
|
for name, fun := range map[string]func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error){
|
||||||
_, _, err := tok.Transfer(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
"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)
|
require.Error(t, err)
|
||||||
|
_, _, err = tok.MultiTransfer([]TransferParameters{})
|
||||||
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())
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,21 +99,37 @@ func TestTokenTransferTransaction(t *testing.T) {
|
||||||
ta := new(testAct)
|
ta := new(testAct)
|
||||||
tok := New(ta, util.Uint160{1, 2, 3})
|
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){
|
for name, fun := range map[string]func(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error){
|
||||||
tok.TransferTransaction,
|
"TransferTransaction": tok.TransferTransaction,
|
||||||
tok.TransferUnsigned,
|
"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("")
|
t.Run(name, func(t *testing.T) {
|
||||||
_, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
ta.err = errors.New("")
|
||||||
require.Error(t, err)
|
_, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
ta.err = nil
|
ta.err = nil
|
||||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
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)
|
tx, err := fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, ta.tx, tx)
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
_, err = fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
_, err = fun(util.Uint160{3, 2, 1}, util.Uint160{3, 2, 1}, big.NewInt(1), stackitem.NewMap())
|
||||||
require.Error(t, err)
|
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)
|
ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
|
||||||
require.NoError(t, ic.VM.Run())
|
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) {
|
t.Run("none scope", func(t *testing.T) {
|
||||||
act, err := actor.New(c, []actor.SignerAccount{{
|
act, err := actor.New(c, []actor.SignerAccount{{
|
||||||
Signer: transaction.Signer{
|
Signer: transaction.Signer{
|
||||||
|
|
Loading…
Reference in a new issue