Merge pull request #3016 from nspcc-dev/emit-convertible
vm: allow to emit stackitem.Convertible
This commit is contained in:
commit
7b86a54fc0
12 changed files with 620 additions and 23 deletions
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
|
@ -75,19 +76,19 @@ func TestNotary_Pipeline(t *testing.T) {
|
|||
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", multisigHash, int64(depositLock+1))
|
||||
|
||||
// `onPayment`: bad token
|
||||
neoCommitteeInvoker.InvokeFail(t, "only GAS can be accepted for deposit", "transfer", multisigHash, notaryHash, int64(1), []any{nil, int64(depositLock)})
|
||||
neoCommitteeInvoker.InvokeFail(t, "only GAS can be accepted for deposit", "transfer", multisigHash, notaryHash, int64(1), ¬ary.OnNEP17PaymentData{Till: uint32(depositLock)})
|
||||
|
||||
// `onPayment`: insufficient first deposit
|
||||
gasCommitteeInvoker.InvokeFail(t, "first deposit can not be less then", "transfer", multisigHash, notaryHash, int64(2*feePerKey-1), []any{nil, int64(depositLock)})
|
||||
gasCommitteeInvoker.InvokeFail(t, "first deposit can not be less then", "transfer", multisigHash, notaryHash, int64(2*feePerKey-1), ¬ary.OnNEP17PaymentData{Till: uint32(depositLock)})
|
||||
|
||||
// `onPayment`: invalid `data` (missing `till` parameter)
|
||||
gasCommitteeInvoker.InvokeFail(t, "`data` parameter should be an array of 2 elements", "transfer", multisigHash, notaryHash, 2*feePerKey, []any{nil})
|
||||
|
||||
// `onPayment`: invalid `data` (outdated `till` parameter)
|
||||
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, 2*feePerKey, []any{nil, int64(0)})
|
||||
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, 2*feePerKey, ¬ary.OnNEP17PaymentData{})
|
||||
|
||||
// `onPayment`: good
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*feePerKey, []any{nil, int64(depositLock)})
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*feePerKey, ¬ary.OnNEP17PaymentData{Till: uint32(depositLock)})
|
||||
checkBalanceOf(t, notaryHash, 2*feePerKey)
|
||||
|
||||
// `expirationOf`: check `till` was set
|
||||
|
@ -97,7 +98,7 @@ func TestNotary_Pipeline(t *testing.T) {
|
|||
notaryCommitteeInvoker.Invoke(t, 2*feePerKey, "balanceOf", multisigHash)
|
||||
|
||||
// `onPayment`: good second deposit and explicit `to` paramenter
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, feePerKey, []any{multisigHash, int64(depositLock + 1)})
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, feePerKey, ¬ary.OnNEP17PaymentData{Account: &multisigHash, Till: uint32(depositLock + 1)})
|
||||
checkBalanceOf(t, notaryHash, 3*feePerKey)
|
||||
|
||||
// `balanceOf`: check deposited amount for the multisig account
|
||||
|
@ -107,17 +108,17 @@ func TestNotary_Pipeline(t *testing.T) {
|
|||
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
|
||||
|
||||
// `onPayment`: empty payment, should fail because `till` less then the previous one
|
||||
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the previous value", "transfer", multisigHash, notaryHash, int64(0), []any{multisigHash, int64(depositLock)})
|
||||
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the previous value", "transfer", multisigHash, notaryHash, int64(0), ¬ary.OnNEP17PaymentData{Account: &multisigHash, Till: uint32(depositLock)})
|
||||
checkBalanceOf(t, notaryHash, 3*feePerKey)
|
||||
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
|
||||
|
||||
// `onPayment`: empty payment, should fail because `till` less then the chain height
|
||||
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, int64(0), []any{multisigHash, int64(1)})
|
||||
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, int64(0), ¬ary.OnNEP17PaymentData{Account: &multisigHash, Till: uint32(1)})
|
||||
checkBalanceOf(t, notaryHash, 3*feePerKey)
|
||||
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
|
||||
|
||||
// `onPayment`: empty payment, should successfully update `till`
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, int64(0), []any{multisigHash, int64(depositLock + 2)})
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, int64(0), ¬ary.OnNEP17PaymentData{Account: &multisigHash, Till: uint32(depositLock + 2)})
|
||||
checkBalanceOf(t, notaryHash, 3*feePerKey)
|
||||
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
||||
|
||||
|
@ -159,12 +160,12 @@ func TestNotary_Pipeline(t *testing.T) {
|
|||
notaryCommitteeInvoker.Invoke(t, false, "withdraw", multisigHash, accHash)
|
||||
|
||||
// `onPayment`: good first deposit to other account, should set default `till` even if other `till` value is provided
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*feePerKey, []any{accHash, int64(math.MaxUint32 - 1)})
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*feePerKey, ¬ary.OnNEP17PaymentData{Account: &accHash, Till: uint32(math.MaxUint32 - 1)})
|
||||
checkBalanceOf(t, notaryHash, 2*feePerKey)
|
||||
notaryCommitteeInvoker.Invoke(t, 5760+e.Chain.BlockHeight()-1, "expirationOf", accHash)
|
||||
|
||||
// `onPayment`: good second deposit to other account, shouldn't update `till` even if other `till` value is provided
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, feePerKey, []any{accHash, int64(math.MaxUint32 - 1)})
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, feePerKey, ¬ary.OnNEP17PaymentData{Account: &accHash, Till: uint32(math.MaxUint32 - 1)})
|
||||
checkBalanceOf(t, notaryHash, 3*feePerKey)
|
||||
notaryCommitteeInvoker.Invoke(t, 5760+e.Chain.BlockHeight()-3, "expirationOf", accHash)
|
||||
}
|
||||
|
@ -201,7 +202,7 @@ func TestNotary_NotaryNodesReward(t *testing.T) {
|
|||
if !spendFullDeposit {
|
||||
depositAmount += 1_0000
|
||||
}
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, depositAmount, []any{multisigHash, e.Chain.BlockHeight() + 1})
|
||||
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, depositAmount, ¬ary.OnNEP17PaymentData{Account: &multisigHash, Till: e.Chain.BlockHeight() + 1})
|
||||
|
||||
// send transaction with Notary contract as a sender
|
||||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1_000_000)
|
||||
|
|
|
@ -217,6 +217,22 @@ func TestReaderTokensOf(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type tData struct {
|
||||
someInt int
|
||||
someString string
|
||||
}
|
||||
|
||||
func (d *tData) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.Make(d.someInt),
|
||||
stackitem.Make(d.someString),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (d *tData) FromStackItem(si stackitem.Item) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func TestTokenTransfer(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tok := NewBase(ta, util.Uint160{1, 2, 3})
|
||||
|
@ -233,7 +249,18 @@ func TestTokenTransfer(t *testing.T) {
|
|||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = tok.Transfer(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, stackitem.NewMap())
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
h, vub, err = tok.Transfer(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, &tData{
|
||||
someInt: 5,
|
||||
someString: "ur",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = tok.Transfer(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, stackitem.NewPointer(123, []byte{123}))
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -255,7 +282,16 @@ func TestTokenTransferTransaction(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = fun(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, stackitem.NewMap())
|
||||
ta.err = nil
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err = fun(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, &tData{
|
||||
someInt: 5,
|
||||
someString: "ur",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = fun(util.Uint160{3, 2, 1}, []byte{3, 2, 1}, stackitem.NewInterop(nil))
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,7 +190,18 @@ func TestDivisibleTransfer(t *testing.T) {
|
|||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = tok.TransferD(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, stackitem.NewMap())
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
h, vub, err = tok.TransferD(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, &tData{
|
||||
someInt: 5,
|
||||
someString: "ur",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
_, _, err = tok.TransferD(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, stackitem.NewInterop(nil))
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -212,7 +223,16 @@ func TestDivisibleTransferTransaction(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = fun(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, stackitem.NewMap())
|
||||
ta.err = nil
|
||||
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||
tx, err = fun(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, &tData{
|
||||
someInt: 5,
|
||||
someString: "ur",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = fun(util.Uint160{1, 2, 3}, util.Uint160{3, 2, 1}, big.NewInt(10), []byte{3, 2, 1}, stackitem.NewInterop(nil))
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,22 @@ func TestReaderBalanceOf(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
}
|
||||
|
||||
type tData struct {
|
||||
someInt int
|
||||
someString string
|
||||
}
|
||||
|
||||
func (d *tData) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.Make(d.someInt),
|
||||
stackitem.Make(d.someString),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (d *tData) FromStackItem(si stackitem.Item) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func TestTokenTransfer(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
tok := New(ta, util.Uint160{1, 2, 3})
|
||||
|
@ -85,7 +101,18 @@ func TestTokenTransfer(t *testing.T) {
|
|||
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())
|
||||
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), &tData{
|
||||
someInt: 5,
|
||||
someString: "ur",
|
||||
})
|
||||
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.NewInterop(nil))
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
@ -120,7 +147,16 @@ func TestTokenTransferTransaction(t *testing.T) {
|
|||
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())
|
||||
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), &tData{
|
||||
someInt: 5,
|
||||
someString: "ur",
|
||||
})
|
||||
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.NewInterop(nil))
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ creation of notary requests.
|
|||
package notary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
|
@ -18,6 +20,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -68,6 +71,10 @@ type OnNEP17PaymentData struct {
|
|||
Till uint32
|
||||
}
|
||||
|
||||
// OnNEP17PaymentData have to implement stackitem.Convertible interface to be
|
||||
// compatible with emit package.
|
||||
var _ = stackitem.Convertible(&OnNEP17PaymentData{})
|
||||
|
||||
// Hash stores the hash of the native Notary contract.
|
||||
var Hash = state.CreateNativeContractHash(nativenames.Notary)
|
||||
|
||||
|
@ -234,3 +241,48 @@ func withdrawScript(from util.Uint160, to util.Uint160) []byte {
|
|||
script, _ := smartcontract.CreateCallWithAssertScript(Hash, "withdraw", from.BytesBE(), to.BytesBE())
|
||||
return script
|
||||
}
|
||||
|
||||
// ToStackItem implements stackitem.Convertible interface.
|
||||
func (d *OnNEP17PaymentData) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make(d.Account),
|
||||
stackitem.Make(d.Till),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromStackItem implements stackitem.Convertible interface.
|
||||
func (d *OnNEP17PaymentData) FromStackItem(si stackitem.Item) error {
|
||||
arr, ok := si.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected stackitem type: %s", si.Type())
|
||||
}
|
||||
if len(arr) != 2 {
|
||||
return fmt.Errorf("unexpected number of fields: %d vs %d", len(arr), 2)
|
||||
}
|
||||
|
||||
if arr[0] != stackitem.Item(stackitem.Null{}) {
|
||||
accBytes, err := arr[0].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve account bytes: %w", err)
|
||||
}
|
||||
acc, err := util.Uint160DecodeBytesBE(accBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode account bytes: %w", err)
|
||||
}
|
||||
d.Account = &acc
|
||||
}
|
||||
till, err := arr[1].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve till: %w", err)
|
||||
}
|
||||
if !till.IsInt64() {
|
||||
return errors.New("till is not an int64")
|
||||
}
|
||||
val := till.Int64()
|
||||
if val > math.MaxUint32 {
|
||||
return fmt.Errorf("till is larger than max uint32 value: %d", val)
|
||||
}
|
||||
d.Till = uint32(val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ package notary
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -197,3 +200,75 @@ func TestTxMakers(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnNEP17PaymentData_Convertible(t *testing.T) {
|
||||
t.Run("non-empty owner", func(t *testing.T) {
|
||||
d := &OnNEP17PaymentData{
|
||||
Account: &util.Uint160{1, 2, 3},
|
||||
Till: 123,
|
||||
}
|
||||
testserdes.ToFromStackItem(t, d, new(OnNEP17PaymentData))
|
||||
})
|
||||
t.Run("empty owner", func(t *testing.T) {
|
||||
d := &OnNEP17PaymentData{
|
||||
Account: nil,
|
||||
Till: 123,
|
||||
}
|
||||
testserdes.ToFromStackItem(t, d, new(OnNEP17PaymentData))
|
||||
})
|
||||
}
|
||||
|
||||
func TestOnNEP17PaymentDataToStackItem(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
data *OnNEP17PaymentData
|
||||
expected stackitem.Item
|
||||
}{
|
||||
"non-empty owner": {
|
||||
data: &OnNEP17PaymentData{
|
||||
Account: &util.Uint160{1, 2, 3},
|
||||
Till: 123,
|
||||
},
|
||||
expected: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make(util.Uint160{1, 2, 3}),
|
||||
stackitem.Make(123),
|
||||
}),
|
||||
},
|
||||
"empty owner": {
|
||||
data: &OnNEP17PaymentData{
|
||||
Account: nil,
|
||||
Till: 123,
|
||||
},
|
||||
expected: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Null{},
|
||||
stackitem.Make(123),
|
||||
}),
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actual, err := tc.data.ToStackItem()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnNEP17PaymentData_FromStackItem(t *testing.T) {
|
||||
errCases := map[string]stackitem.Item{
|
||||
"unexpected stackitem type": stackitem.NewBool(true),
|
||||
"unexpected number of fields": stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true)}),
|
||||
"failed to retrieve account bytes": stackitem.NewArray([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Make(1)}),
|
||||
"failed to decode account bytes": stackitem.NewArray([]stackitem.Item{stackitem.Make([]byte{1}), stackitem.Make(1)}),
|
||||
"failed to retrieve till": stackitem.NewArray([]stackitem.Item{stackitem.Make(util.Uint160{1}), stackitem.NewInterop(nil)}),
|
||||
"till is not an int64": stackitem.NewArray([]stackitem.Item{stackitem.Make(util.Uint160{1}), stackitem.NewBigInteger(new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)))}),
|
||||
"till is larger than max uint32 value": stackitem.NewArray([]stackitem.Item{stackitem.Make(util.Uint160{1}), stackitem.Make(math.MaxUint32 + 1)}),
|
||||
}
|
||||
for name, errCase := range errCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
d := new(OnNEP17PaymentData)
|
||||
err := d.FromStackItem(errCase)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), name), name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func ExampleActor() {
|
|||
// Transfer some GAS to the Notary contract to be able to send notary requests
|
||||
// from the first account.
|
||||
gasSingle := gas.New(single)
|
||||
txid, vub, _ := gasSingle.Transfer(single.Sender(), notary.Hash, big.NewInt(10_0000_0000), notary.OnNEP17PaymentData{Till: 10000000})
|
||||
txid, vub, _ := gasSingle.Transfer(single.Sender(), notary.Hash, big.NewInt(10_0000_0000), ¬ary.OnNEP17PaymentData{Till: 10000000})
|
||||
|
||||
var depositOK bool
|
||||
// Wait for transaction to be persisted, either it gets in and we get
|
||||
|
|
|
@ -41,9 +41,10 @@ func NewBuilder() *Builder {
|
|||
|
||||
// InvokeMethod is the most generic contract method invoker, the code it produces
|
||||
// packs all of the arguments given into an array and calls some method of the
|
||||
// contract. The correctness of this invocation (number and type of parameters) is
|
||||
// out of scope of this method, as well as return value, if contract's method returns
|
||||
// something this value just remains on the execution stack.
|
||||
// contract. It accepts as parameters everything that emit.Array accepts. The
|
||||
// correctness of this invocation (number and type of parameters) is out of scope
|
||||
// of this method, as well as return value, if contract's method returns something
|
||||
// this value just remains on the execution stack.
|
||||
func (b *Builder) InvokeMethod(contract util.Uint160, method string, params ...any) {
|
||||
emit.AppCall(b.bw.BinWriter, contract, method, callflag.All, params...)
|
||||
}
|
||||
|
|
|
@ -98,7 +98,16 @@ func bigInt(w *io.BinWriter, n *big.Int, trySmall bool) {
|
|||
w.WriteBytes(padRight(1<<padSize, buf))
|
||||
}
|
||||
|
||||
// Array emits an array of elements to the given buffer.
|
||||
// Array emits an array of elements to the given buffer. It accepts elements of the following types:
|
||||
// - int8, int16, int32, int64, int
|
||||
// - uint8, uint16, uint32, uint64, uint
|
||||
// - *big.Int
|
||||
// - string, []byte
|
||||
// - util.Uint160, *util.Uint160, util.Uint256, *util.Uint256
|
||||
// - bool
|
||||
// - stackitem.Convertible, stackitem.Item
|
||||
// - nil
|
||||
// - []any
|
||||
func Array(w *io.BinWriter, es ...any) {
|
||||
if len(es) == 0 {
|
||||
Opcodes(w, opcode.NEWARRAY0)
|
||||
|
@ -110,6 +119,8 @@ func Array(w *io.BinWriter, es ...any) {
|
|||
Array(w, e...)
|
||||
case int64:
|
||||
Int(w, e)
|
||||
case uint64:
|
||||
BigInt(w, new(big.Int).SetUint64(e))
|
||||
case int32:
|
||||
Int(w, int64(e))
|
||||
case uint32:
|
||||
|
@ -124,6 +135,8 @@ func Array(w *io.BinWriter, es ...any) {
|
|||
Int(w, int64(e))
|
||||
case int:
|
||||
Int(w, int64(e))
|
||||
case uint:
|
||||
BigInt(w, new(big.Int).SetUint64(uint64(e)))
|
||||
case *big.Int:
|
||||
BigInt(w, e)
|
||||
case string:
|
||||
|
@ -148,6 +161,10 @@ func Array(w *io.BinWriter, es ...any) {
|
|||
Bytes(w, e)
|
||||
case bool:
|
||||
Bool(w, e)
|
||||
case stackitem.Convertible:
|
||||
Convertible(w, e)
|
||||
case stackitem.Item:
|
||||
StackItem(w, e)
|
||||
default:
|
||||
if es[i] != nil {
|
||||
w.Err = fmt.Errorf("unsupported type: %T", e)
|
||||
|
@ -160,6 +177,63 @@ func Array(w *io.BinWriter, es ...any) {
|
|||
Opcodes(w, opcode.PACK)
|
||||
}
|
||||
|
||||
// Convertible converts provided stackitem.Convertible to the stackitem.Item and
|
||||
// emits the item to the given buffer.
|
||||
func Convertible(w *io.BinWriter, c stackitem.Convertible) {
|
||||
si, err := c.ToStackItem()
|
||||
if err != nil {
|
||||
w.Err = fmt.Errorf("failed to convert stackitem.Convertible to stackitem: %w", err)
|
||||
return
|
||||
}
|
||||
StackItem(w, si)
|
||||
}
|
||||
|
||||
// StackItem emits provided stackitem.Item to the given buffer.
|
||||
func StackItem(w *io.BinWriter, si stackitem.Item) {
|
||||
switch t := si.Type(); t {
|
||||
case stackitem.AnyT:
|
||||
if si.Value() == nil {
|
||||
Opcodes(w, opcode.PUSHNULL)
|
||||
} else {
|
||||
w.Err = fmt.Errorf("only nil value supported for %s", t)
|
||||
return
|
||||
}
|
||||
case stackitem.BooleanT:
|
||||
Bool(w, si.Value().(bool))
|
||||
case stackitem.IntegerT:
|
||||
BigInt(w, si.Value().(*big.Int))
|
||||
case stackitem.ByteArrayT, stackitem.BufferT:
|
||||
Bytes(w, si.Value().([]byte))
|
||||
case stackitem.ArrayT:
|
||||
arr := si.Value().([]stackitem.Item)
|
||||
arrAny := make([]any, len(arr))
|
||||
for i := range arr {
|
||||
arrAny[i] = arr[i]
|
||||
}
|
||||
Array(w, arrAny...)
|
||||
case stackitem.StructT:
|
||||
arr := si.Value().([]stackitem.Item)
|
||||
for i := len(arr) - 1; i >= 0; i-- {
|
||||
StackItem(w, arr[i])
|
||||
}
|
||||
|
||||
Int(w, int64(len(arr)))
|
||||
Opcodes(w, opcode.PACKSTRUCT)
|
||||
case stackitem.MapT:
|
||||
arr := si.Value().([]stackitem.MapElement)
|
||||
for i := len(arr) - 1; i >= 0; i-- {
|
||||
StackItem(w, arr[i].Value)
|
||||
StackItem(w, arr[i].Key)
|
||||
}
|
||||
|
||||
Int(w, int64(len(arr)))
|
||||
Opcodes(w, opcode.PACKMAP)
|
||||
default:
|
||||
w.Err = fmt.Errorf("%s is unsuppoted", t)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// String emits a string to the given buffer.
|
||||
func String(w *io.BinWriter, s string) {
|
||||
Bytes(w, []byte(s))
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
|
@ -223,7 +224,34 @@ func TestEmitArray(t *testing.T) {
|
|||
u256 := util.Uint256{1, 2, 3}
|
||||
veryBig := new(big.Int).SetUint64(math.MaxUint64)
|
||||
veryBig.Add(veryBig, big.NewInt(1))
|
||||
Array(buf.BinWriter, p160, p256, &u160, &u256, u160, u256, big.NewInt(0), veryBig,
|
||||
Array(buf.BinWriter,
|
||||
uint64(math.MaxUint64),
|
||||
uint(math.MaxUint32), // don't use MaxUint to keep test results the same throughout all platforms.
|
||||
stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{
|
||||
Key: stackitem.Make(1),
|
||||
Value: stackitem.Make("str1"),
|
||||
},
|
||||
{
|
||||
Key: stackitem.Make(2),
|
||||
Value: stackitem.Make("str2"),
|
||||
},
|
||||
}),
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.Make(4),
|
||||
stackitem.Make("str"),
|
||||
}),
|
||||
&ConvertibleStruct{
|
||||
SomeInt: 5,
|
||||
SomeString: "str",
|
||||
},
|
||||
stackitem.Make(5),
|
||||
stackitem.Make("str"),
|
||||
stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make(true),
|
||||
stackitem.Make("str"),
|
||||
}),
|
||||
p160, p256, &u160, &u256, u160, u256, big.NewInt(0), veryBig,
|
||||
[]any{int64(1), int64(2)}, nil, int64(1), "str", false, true, []byte{0xCA, 0xFE})
|
||||
require.NoError(t, buf.Err)
|
||||
|
||||
|
@ -259,6 +287,65 @@ func TestEmitArray(t *testing.T) {
|
|||
assert.EqualValues(t, u160.BytesBE(), res[127:147])
|
||||
assert.EqualValues(t, opcode.PUSHNULL, res[147])
|
||||
assert.EqualValues(t, opcode.PUSHNULL, res[148])
|
||||
// Array of two stackitems:
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[149])
|
||||
assert.EqualValues(t, 3, res[150])
|
||||
assert.EqualValues(t, []byte("str"), res[151:154])
|
||||
assert.EqualValues(t, opcode.PUSHT, res[154])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[155])
|
||||
assert.EqualValues(t, opcode.PACK, res[156])
|
||||
// ByteString stackitem ("str"):
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[157])
|
||||
assert.EqualValues(t, 3, res[158])
|
||||
assert.EqualValues(t, []byte("str"), res[159:162])
|
||||
// Integer stackitem (5):
|
||||
assert.EqualValues(t, opcode.PUSH5, res[162])
|
||||
// Convertible struct:
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[163])
|
||||
assert.EqualValues(t, 3, res[164])
|
||||
assert.EqualValues(t, []byte("str"), res[165:168])
|
||||
assert.EqualValues(t, opcode.PUSH5, res[168])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[169])
|
||||
assert.EqualValues(t, opcode.PACK, res[170])
|
||||
// Struct stackitem (4, "str")
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[171])
|
||||
assert.EqualValues(t, 3, res[172])
|
||||
assert.EqualValues(t, []byte("str"), res[173:176])
|
||||
assert.EqualValues(t, opcode.PUSH4, res[176])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[177])
|
||||
assert.EqualValues(t, opcode.PACKSTRUCT, res[178])
|
||||
// Map stackitem (1:"str1", 2:"str2")
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[179])
|
||||
assert.EqualValues(t, 4, res[180])
|
||||
assert.EqualValues(t, []byte("str2"), res[181:185])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[185])
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[186])
|
||||
assert.EqualValues(t, 4, res[187])
|
||||
assert.EqualValues(t, []byte("str1"), res[188:192])
|
||||
assert.EqualValues(t, opcode.PUSH1, res[192])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[193])
|
||||
assert.EqualValues(t, opcode.PACKMAP, res[194])
|
||||
// uint (MaxUint32)
|
||||
assert.EqualValues(t, opcode.PUSHINT64, res[195])
|
||||
assert.EqualValues(t, []byte{
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0, 0, 0, 0,
|
||||
}, res[196:204])
|
||||
// uint64 (MaxUint64)
|
||||
assert.EqualValues(t, opcode.PUSHINT128, res[204])
|
||||
assert.EqualValues(t, []byte{
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0}, res[205:221])
|
||||
|
||||
// Values packing:
|
||||
assert.EqualValues(t, opcode.PUSHINT8, res[221])
|
||||
assert.EqualValues(t, byte(23), res[222])
|
||||
assert.EqualValues(t, opcode.PACK, res[223])
|
||||
|
||||
// Overall script length:
|
||||
assert.EqualValues(t, 224, len(res))
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
|
@ -374,3 +461,191 @@ func TestEmitCall(t *testing.T) {
|
|||
label := binary.LittleEndian.Uint16(result[1:3])
|
||||
assert.Equal(t, label, uint16(100))
|
||||
}
|
||||
|
||||
func TestEmitStackitem(t *testing.T) {
|
||||
t.Run("good", func(t *testing.T) {
|
||||
buf := io.NewBufBinWriter()
|
||||
itms := []stackitem.Item{
|
||||
stackitem.Make(true),
|
||||
stackitem.Make(false),
|
||||
stackitem.Make(5),
|
||||
stackitem.Make("str"),
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(true),
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(1),
|
||||
stackitem.Make("str"),
|
||||
}),
|
||||
}),
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.Make(true),
|
||||
stackitem.Make(7),
|
||||
}),
|
||||
stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{
|
||||
Key: stackitem.Make(7),
|
||||
Value: stackitem.Make("str1"),
|
||||
},
|
||||
{
|
||||
Key: stackitem.Make(8),
|
||||
Value: stackitem.Make("str2"),
|
||||
},
|
||||
}),
|
||||
stackitem.Null{},
|
||||
}
|
||||
for _, si := range itms {
|
||||
StackItem(buf.BinWriter, si)
|
||||
}
|
||||
require.NoError(t, buf.Err)
|
||||
res := buf.Bytes()
|
||||
|
||||
// Single values:
|
||||
assert.EqualValues(t, opcode.PUSHT, res[0])
|
||||
assert.EqualValues(t, opcode.PUSHF, res[1])
|
||||
assert.EqualValues(t, opcode.PUSH5, res[2])
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[3])
|
||||
assert.EqualValues(t, 3, res[4])
|
||||
assert.EqualValues(t, []byte("str"), res[5:8])
|
||||
// Nested array:
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[8])
|
||||
assert.EqualValues(t, 3, res[9])
|
||||
assert.EqualValues(t, []byte("str"), res[10:13])
|
||||
assert.EqualValues(t, opcode.PUSH1, res[13])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[14])
|
||||
assert.EqualValues(t, opcode.PACK, res[15])
|
||||
assert.EqualValues(t, opcode.PUSHT, res[16])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[17])
|
||||
assert.EqualValues(t, opcode.PACK, res[18])
|
||||
// Struct (true, 7):
|
||||
assert.EqualValues(t, opcode.PUSH7, res[19])
|
||||
assert.EqualValues(t, opcode.PUSHT, res[20])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[21])
|
||||
assert.EqualValues(t, opcode.PACKSTRUCT, res[22])
|
||||
// Map (7:"str1", 8:"str2"):
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[23])
|
||||
assert.EqualValues(t, 4, res[24])
|
||||
assert.EqualValues(t, []byte("str2"), res[25:29])
|
||||
assert.EqualValues(t, opcode.PUSH8, res[29])
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[30])
|
||||
assert.EqualValues(t, 4, res[31])
|
||||
assert.EqualValues(t, []byte("str1"), res[32:36])
|
||||
assert.EqualValues(t, opcode.PUSH7, res[36])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[37])
|
||||
assert.EqualValues(t, opcode.PACKMAP, res[38])
|
||||
// Null:
|
||||
assert.EqualValues(t, opcode.PUSHNULL, res[39])
|
||||
|
||||
// Overall script length:
|
||||
require.Equal(t, 40, len(res))
|
||||
})
|
||||
|
||||
t.Run("unsupported", func(t *testing.T) {
|
||||
itms := []stackitem.Item{
|
||||
stackitem.NewInterop(nil),
|
||||
stackitem.NewPointer(123, []byte{123}),
|
||||
}
|
||||
for _, si := range itms {
|
||||
buf := io.NewBufBinWriter()
|
||||
StackItem(buf.BinWriter, si)
|
||||
require.Error(t, buf.Err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid any", func(t *testing.T) {
|
||||
buf := io.NewBufBinWriter()
|
||||
StackItem(buf.BinWriter, StrangeStackItem{})
|
||||
actualErr := buf.Err
|
||||
require.Error(t, actualErr)
|
||||
require.True(t, strings.Contains(actualErr.Error(), "only nil value supported"), actualErr.Error())
|
||||
})
|
||||
}
|
||||
|
||||
type StrangeStackItem struct{}
|
||||
|
||||
var _ = stackitem.Item(StrangeStackItem{})
|
||||
|
||||
func (StrangeStackItem) Value() any {
|
||||
return struct{}{}
|
||||
}
|
||||
func (StrangeStackItem) Type() stackitem.Type {
|
||||
return stackitem.AnyT
|
||||
}
|
||||
func (StrangeStackItem) String() string {
|
||||
panic("TODO")
|
||||
}
|
||||
func (StrangeStackItem) Dup() stackitem.Item {
|
||||
panic("TODO")
|
||||
}
|
||||
func (StrangeStackItem) TryBool() (bool, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (StrangeStackItem) TryBytes() ([]byte, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (StrangeStackItem) TryInteger() (*big.Int, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (StrangeStackItem) Equals(stackitem.Item) bool {
|
||||
panic("TODO")
|
||||
}
|
||||
func (StrangeStackItem) Convert(stackitem.Type) (stackitem.Item, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
type ConvertibleStruct struct {
|
||||
SomeInt int
|
||||
SomeString string
|
||||
err error
|
||||
}
|
||||
|
||||
var _ = stackitem.Convertible(&ConvertibleStruct{})
|
||||
|
||||
func (s *ConvertibleStruct) ToStackItem() (stackitem.Item, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
return stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make(s.SomeInt),
|
||||
stackitem.Make(s.SomeString),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *ConvertibleStruct) FromStackItem(si stackitem.Item) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func TestEmitConvertible(t *testing.T) {
|
||||
t.Run("good", func(t *testing.T) {
|
||||
buf := io.NewBufBinWriter()
|
||||
str := &ConvertibleStruct{
|
||||
SomeInt: 5,
|
||||
SomeString: "str",
|
||||
}
|
||||
Convertible(buf.BinWriter, str)
|
||||
require.NoError(t, buf.Err)
|
||||
res := buf.Bytes()
|
||||
|
||||
// The struct itself:
|
||||
assert.EqualValues(t, opcode.PUSHDATA1, res[0])
|
||||
assert.EqualValues(t, 3, res[1])
|
||||
assert.EqualValues(t, []byte("str"), res[2:5])
|
||||
assert.EqualValues(t, opcode.PUSH5, res[5])
|
||||
assert.EqualValues(t, opcode.PUSH2, res[6])
|
||||
assert.EqualValues(t, opcode.PACK, res[7])
|
||||
|
||||
// Overall length:
|
||||
assert.EqualValues(t, 8, len(res))
|
||||
})
|
||||
|
||||
t.Run("error on conversion", func(t *testing.T) {
|
||||
buf := io.NewBufBinWriter()
|
||||
expectedErr := errors.New("error on conversion")
|
||||
str := &ConvertibleStruct{
|
||||
err: expectedErr,
|
||||
}
|
||||
Convertible(buf.BinWriter, str)
|
||||
actualErr := buf.Err
|
||||
require.Error(t, actualErr)
|
||||
require.ErrorIs(t, actualErr, expectedErr)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -132,6 +132,16 @@ func Make(v any) Item {
|
|||
return Make(val.BytesBE())
|
||||
case util.Uint256:
|
||||
return Make(val.BytesBE())
|
||||
case *util.Uint160:
|
||||
if val == nil {
|
||||
return Null{}
|
||||
}
|
||||
return Make(*val)
|
||||
case *util.Uint256:
|
||||
if val == nil {
|
||||
return Null{}
|
||||
}
|
||||
return Make(*val)
|
||||
case nil:
|
||||
return Null{}
|
||||
default:
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -81,6 +82,22 @@ var makeStackItemTestCases = []struct {
|
|||
input: nil,
|
||||
result: Null{},
|
||||
},
|
||||
{
|
||||
input: &util.Uint160{1, 2, 3},
|
||||
result: NewByteArray(util.Uint160{1, 2, 3}.BytesBE()),
|
||||
},
|
||||
{
|
||||
input: &util.Uint256{1, 2, 3},
|
||||
result: NewByteArray(util.Uint256{1, 2, 3}.BytesBE()),
|
||||
},
|
||||
{
|
||||
input: (*util.Uint160)(nil),
|
||||
result: Null{},
|
||||
},
|
||||
{
|
||||
input: (*util.Uint256)(nil),
|
||||
result: Null{},
|
||||
},
|
||||
}
|
||||
|
||||
var makeStackItemErrorCases = []struct {
|
||||
|
|
Loading…
Reference in a new issue