Merge pull request #1587 from nspcc-dev/tests/interop
Extend test suite for interops
This commit is contained in:
commit
2d7b823f25
17 changed files with 1066 additions and 415 deletions
57
pkg/core/interop/binary/encode.go
Normal file
57
pkg/core/interop/binary/encode.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
// Serialize serializes top stack item into a ByteArray.
|
||||
func Serialize(ic *interop.Context) error {
|
||||
return vm.RuntimeSerialize(ic.VM)
|
||||
}
|
||||
|
||||
// Deserialize deserializes ByteArray from a stack into an item.
|
||||
func Deserialize(ic *interop.Context) error {
|
||||
return vm.RuntimeDeserialize(ic.VM)
|
||||
}
|
||||
|
||||
// EncodeBase64 encodes top stack item into a base64 string.
|
||||
func EncodeBase64(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().Bytes()
|
||||
result := base64.StdEncoding.EncodeToString(src)
|
||||
ic.VM.Estack().PushVal([]byte(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeBase64 decodes top stack item from base64 string to byte array.
|
||||
func DecodeBase64(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().String()
|
||||
result, err := base64.StdEncoding.DecodeString(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ic.VM.Estack().PushVal(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBase58 encodes top stack item into a base58 string.
|
||||
func EncodeBase58(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().Bytes()
|
||||
result := base58.Encode(src)
|
||||
ic.VM.Estack().PushVal([]byte(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeBase58 decodes top stack item from base58 string to byte array.
|
||||
func DecodeBase58(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().String()
|
||||
result, err := base58.Decode(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ic.VM.Estack().PushVal(result)
|
||||
return nil
|
||||
}
|
92
pkg/core/interop/binary/encode_test.go
Normal file
92
pkg/core/interop/binary/encode_test.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRuntimeSerialize(t *testing.T) {
|
||||
t.Run("recursive", func(t *testing.T) {
|
||||
arr := stackitem.NewArray(nil)
|
||||
arr.Append(arr)
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.Estack().PushVal(arr)
|
||||
require.Error(t, Serialize(ic))
|
||||
})
|
||||
t.Run("big item", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.Estack().PushVal(make([]byte, stackitem.MaxSize))
|
||||
require.Error(t, Serialize(ic))
|
||||
})
|
||||
t.Run("good", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.Estack().PushVal(42)
|
||||
require.NoError(t, Serialize(ic))
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
stackitem.EncodeBinaryStackItem(stackitem.Make(42), w.BinWriter)
|
||||
require.NoError(t, w.Err)
|
||||
|
||||
encoded := w.Bytes()
|
||||
require.Equal(t, encoded, ic.VM.Estack().Pop().Bytes())
|
||||
|
||||
ic.VM.Estack().PushVal(encoded)
|
||||
require.NoError(t, Deserialize(ic))
|
||||
require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().BigInt())
|
||||
|
||||
t.Run("bad", func(t *testing.T) {
|
||||
encoded[0] ^= 0xFF
|
||||
ic.VM.Estack().PushVal(encoded)
|
||||
require.Error(t, Deserialize(ic))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuntimeEncodeDecode(t *testing.T) {
|
||||
original := []byte("my pretty string")
|
||||
encoded64 := base64.StdEncoding.EncodeToString(original)
|
||||
encoded58 := base58.Encode(original)
|
||||
v := vm.New()
|
||||
ic := &interop.Context{VM: v}
|
||||
|
||||
t.Run("Encode64", func(t *testing.T) {
|
||||
v.Estack().PushVal(original)
|
||||
require.NoError(t, EncodeBase64(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, []byte(encoded64), actual)
|
||||
})
|
||||
t.Run("Encode58", func(t *testing.T) {
|
||||
v.Estack().PushVal(original)
|
||||
require.NoError(t, EncodeBase58(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, []byte(encoded58), actual)
|
||||
})
|
||||
t.Run("Decode64/positive", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded64)
|
||||
require.NoError(t, DecodeBase64(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, original, actual)
|
||||
})
|
||||
t.Run("Decode64/error", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded64 + "%")
|
||||
require.Error(t, DecodeBase64(ic))
|
||||
})
|
||||
t.Run("Decode58/positive", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded58)
|
||||
require.NoError(t, DecodeBase58(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, original, actual)
|
||||
})
|
||||
t.Run("Decode58/error", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded58 + "%")
|
||||
require.Error(t, DecodeBase58(ic))
|
||||
})
|
||||
}
|
|
@ -2,9 +2,14 @@ package crypto
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"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/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
|
@ -14,14 +19,116 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func initCHECKMULTISIG(msg []byte, n int) ([]stackitem.Item, []stackitem.Item, map[string]*keys.PublicKey, error) {
|
||||
func TestECDSASecp256r1Verify(t *testing.T) {
|
||||
testECDSAVerify(t, true)
|
||||
}
|
||||
|
||||
func TestECDSASecp256k1Verify(t *testing.T) {
|
||||
testECDSAVerify(t, false)
|
||||
}
|
||||
|
||||
func testECDSAVerify(t *testing.T, isR1 bool) {
|
||||
var priv *keys.PrivateKey
|
||||
var err error
|
||||
if isR1 {
|
||||
priv, err = keys.NewPrivateKey()
|
||||
} else {
|
||||
priv, err = keys.NewSecp256k1PrivateKey()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
verifyFunc := ECDSASecp256r1Verify
|
||||
if !isR1 {
|
||||
verifyFunc = ECDSASecp256k1Verify
|
||||
}
|
||||
d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
|
||||
ic := &interop.Context{DAO: dao.NewCached(d)}
|
||||
runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) {
|
||||
ic.SpawnVM()
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
|
||||
var err error
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic: %v", r)
|
||||
}
|
||||
}()
|
||||
err = verifyFunc(ic)
|
||||
}()
|
||||
|
||||
if isErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, ic.VM.Estack().Len())
|
||||
require.Equal(t, result, ic.VM.Estack().Pop().Value().(bool))
|
||||
}
|
||||
|
||||
msg := []byte("test message")
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, false, true, sign, priv.PublicKey().Bytes(), msg)
|
||||
})
|
||||
|
||||
t.Run("signed interop item", func(t *testing.T) {
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1)
|
||||
msg := tx.GetSignedPart()
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.NewInterop(tx))
|
||||
})
|
||||
|
||||
t.Run("signed script container", func(t *testing.T) {
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1)
|
||||
msg := tx.GetSignedPart()
|
||||
sign := priv.Sign(msg)
|
||||
ic.Container = tx
|
||||
runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.Null{})
|
||||
})
|
||||
|
||||
t.Run("missing arguments", func(t *testing.T) {
|
||||
runCase(t, true, false)
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, true, false, sign)
|
||||
runCase(t, true, false, sign, priv.PublicKey().Bytes())
|
||||
})
|
||||
|
||||
t.Run("invalid signature", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
sign[0] = ^sign[0]
|
||||
runCase(t, false, false, sign, priv.PublicKey().Bytes(), msg)
|
||||
})
|
||||
|
||||
t.Run("invalid public key", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
pub := priv.PublicKey().Bytes()
|
||||
pub[0] = 0xFF // invalid prefix
|
||||
runCase(t, true, false, sign, pub, msg)
|
||||
})
|
||||
|
||||
t.Run("invalid message", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, true, false, sign, priv.PublicKey().Bytes(),
|
||||
stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(msg)}))
|
||||
})
|
||||
}
|
||||
|
||||
func initCHECKMULTISIG(isR1 bool, msg []byte, n int) ([]stackitem.Item, []stackitem.Item, map[string]*keys.PublicKey, error) {
|
||||
var err error
|
||||
|
||||
keyMap := make(map[string]*keys.PublicKey)
|
||||
pkeys := make([]*keys.PrivateKey, n)
|
||||
pubs := make([]stackitem.Item, n)
|
||||
for i := range pubs {
|
||||
pkeys[i], err = keys.NewPrivateKey()
|
||||
if isR1 {
|
||||
pkeys[i], err = keys.NewPrivateKey()
|
||||
} else {
|
||||
pkeys[i], err = keys.NewSecp256k1PrivateKey()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
@ -54,18 +161,27 @@ func subSlice(arr []stackitem.Item, indices []int) []stackitem.Item {
|
|||
return result
|
||||
}
|
||||
|
||||
func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM {
|
||||
func initCheckMultisigVMNoArgs(isR1 bool) *vm.VM {
|
||||
buf := make([]byte, 5)
|
||||
buf[0] = byte(opcode.SYSCALL)
|
||||
binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256r1CheckMultisigID)
|
||||
if isR1 {
|
||||
binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256r1CheckMultisigID)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256k1CheckMultisigID)
|
||||
}
|
||||
|
||||
ic := &interop.Context{Trigger: trigger.Verification}
|
||||
Register(ic)
|
||||
v := ic.SpawnVM()
|
||||
v.LoadScript(buf)
|
||||
return v
|
||||
}
|
||||
|
||||
func initCHECKMULTISIGVM(t *testing.T, isR1 bool, n int, ik, is []int) *vm.VM {
|
||||
v := initCheckMultisigVMNoArgs(isR1)
|
||||
msg := []byte("NEO - An Open Network For Smart Economy")
|
||||
|
||||
pubs, sigs, _, err := initCHECKMULTISIG(msg, n)
|
||||
pubs, sigs, _, err := initCHECKMULTISIG(isR1, msg, n)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubs = subSlice(pubs, ik)
|
||||
|
@ -78,26 +194,34 @@ func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM {
|
|||
return v
|
||||
}
|
||||
|
||||
func testCHECKMULTISIGGood(t *testing.T, n int, is []int) {
|
||||
v := initCHECKMULTISIGVM(t, n, nil, is)
|
||||
func testCHECKMULTISIGGood(t *testing.T, isR1 bool, n int, is []int) {
|
||||
v := initCHECKMULTISIGVM(t, isR1, n, nil, is)
|
||||
|
||||
require.NoError(t, v.Run())
|
||||
assert.Equal(t, 1, v.Estack().Len())
|
||||
assert.True(t, v.Estack().Pop().Bool())
|
||||
}
|
||||
|
||||
func TestCHECKMULTISIGGood(t *testing.T) {
|
||||
t.Run("3_1", func(t *testing.T) { testCHECKMULTISIGGood(t, 3, []int{1}) })
|
||||
t.Run("2_2", func(t *testing.T) { testCHECKMULTISIGGood(t, 2, []int{0, 1}) })
|
||||
t.Run("3_3", func(t *testing.T) { testCHECKMULTISIGGood(t, 3, []int{0, 1, 2}) })
|
||||
t.Run("3_2", func(t *testing.T) { testCHECKMULTISIGGood(t, 3, []int{0, 2}) })
|
||||
t.Run("4_2", func(t *testing.T) { testCHECKMULTISIGGood(t, 4, []int{0, 2}) })
|
||||
t.Run("10_7", func(t *testing.T) { testCHECKMULTISIGGood(t, 10, []int{2, 3, 4, 5, 6, 8, 9}) })
|
||||
t.Run("12_9", func(t *testing.T) { testCHECKMULTISIGGood(t, 12, []int{0, 1, 4, 5, 6, 7, 8, 9}) })
|
||||
func TestECDSASecp256r1CheckMultisigGood(t *testing.T) {
|
||||
testCurveCHECKMULTISIGGood(t, true)
|
||||
}
|
||||
|
||||
func testCHECKMULTISIGBad(t *testing.T, isErr bool, n int, ik, is []int) {
|
||||
v := initCHECKMULTISIGVM(t, n, ik, is)
|
||||
func TestECDSASecp256k1CheckMultisigGood(t *testing.T) {
|
||||
testCurveCHECKMULTISIGGood(t, false)
|
||||
}
|
||||
|
||||
func testCurveCHECKMULTISIGGood(t *testing.T, isR1 bool) {
|
||||
t.Run("3_1", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 3, []int{1}) })
|
||||
t.Run("2_2", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 2, []int{0, 1}) })
|
||||
t.Run("3_3", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 3, []int{0, 1, 2}) })
|
||||
t.Run("3_2", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 3, []int{0, 2}) })
|
||||
t.Run("4_2", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 4, []int{0, 2}) })
|
||||
t.Run("10_7", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 10, []int{2, 3, 4, 5, 6, 8, 9}) })
|
||||
t.Run("12_9", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 12, []int{0, 1, 4, 5, 6, 7, 8, 9}) })
|
||||
}
|
||||
|
||||
func testCHECKMULTISIGBad(t *testing.T, isR1 bool, isErr bool, n int, ik, is []int) {
|
||||
v := initCHECKMULTISIGVM(t, isR1, n, ik, is)
|
||||
|
||||
if isErr {
|
||||
require.Error(t, v.Run())
|
||||
|
@ -108,14 +232,49 @@ func testCHECKMULTISIGBad(t *testing.T, isErr bool, n int, ik, is []int) {
|
|||
assert.False(t, v.Estack().Pop().Bool())
|
||||
}
|
||||
|
||||
func TestCHECKMULTISIGBad(t *testing.T) {
|
||||
t.Run("1_1 wrong signature", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 2, []int{0}, []int{1}) })
|
||||
t.Run("3_2 wrong order", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 3, []int{0, 2}, []int{2, 0}) })
|
||||
t.Run("3_2 duplicate sig", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 3, nil, []int{0, 0}) })
|
||||
t.Run("1_2 too many signatures", func(t *testing.T) { testCHECKMULTISIGBad(t, true, 2, []int{0}, []int{0, 1}) })
|
||||
func TestECDSASecp256r1CheckMultisigBad(t *testing.T) {
|
||||
testCurveCHECKMULTISIGBad(t, true)
|
||||
}
|
||||
|
||||
func TestECDSASecp256k1CheckMultisigBad(t *testing.T) {
|
||||
testCurveCHECKMULTISIGBad(t, false)
|
||||
}
|
||||
|
||||
func testCurveCHECKMULTISIGBad(t *testing.T, isR1 bool) {
|
||||
t.Run("1_1 wrong signature", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, false, 2, []int{0}, []int{1}) })
|
||||
t.Run("3_2 wrong order", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, false, 3, []int{0, 2}, []int{2, 0}) })
|
||||
t.Run("3_2 duplicate sig", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, false, 3, nil, []int{0, 0}) })
|
||||
t.Run("1_2 too many signatures", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, true, 2, []int{0}, []int{0, 1}) })
|
||||
t.Run("gas limit exceeded", func(t *testing.T) {
|
||||
v := initCHECKMULTISIGVM(t, 1, []int{0}, []int{0})
|
||||
v := initCHECKMULTISIGVM(t, isR1, 1, []int{0}, []int{0})
|
||||
v.GasLimit = ECDSAVerifyPrice - 1
|
||||
require.Error(t, v.Run())
|
||||
})
|
||||
|
||||
msg := []byte("NEO - An Open Network For Smart Economy")
|
||||
pubs, sigs, _, err := initCHECKMULTISIG(isR1, msg, 1)
|
||||
require.NoError(t, err)
|
||||
arr := stackitem.NewArray([]stackitem.Item{stackitem.NewArray(nil)})
|
||||
|
||||
t.Run("invalid message type", func(t *testing.T) {
|
||||
v := initCheckMultisigVMNoArgs(isR1)
|
||||
v.Estack().PushVal(sigs)
|
||||
v.Estack().PushVal(pubs)
|
||||
v.Estack().PushVal(stackitem.NewArray(nil))
|
||||
require.Error(t, v.Run())
|
||||
})
|
||||
t.Run("invalid public keys", func(t *testing.T) {
|
||||
v := initCheckMultisigVMNoArgs(isR1)
|
||||
v.Estack().PushVal(sigs)
|
||||
v.Estack().PushVal(arr)
|
||||
v.Estack().PushVal(msg)
|
||||
require.Error(t, v.Run())
|
||||
})
|
||||
t.Run("invalid signatures", func(t *testing.T) {
|
||||
v := initCheckMultisigVMNoArgs(isR1)
|
||||
v.Estack().PushVal(arr)
|
||||
v.Estack().PushVal(pubs)
|
||||
v.Estack().PushVal(msg)
|
||||
require.Error(t, v.Run())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,42 +5,65 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testVerifiable []byte
|
||||
|
||||
var _ crypto.Verifiable = testVerifiable{}
|
||||
|
||||
func (v testVerifiable) GetSignedPart() []byte {
|
||||
return v
|
||||
}
|
||||
func (v testVerifiable) GetSignedHash() util.Uint256 {
|
||||
return hash.Sha256(v)
|
||||
}
|
||||
|
||||
func testHash0100(t *testing.T, result string, interopFunc func(*interop.Context) error) {
|
||||
t.Run("good", func(t *testing.T) {
|
||||
bs := []byte{1, 0}
|
||||
|
||||
checkGood := func(t *testing.T, ic *interop.Context) {
|
||||
require.NoError(t, interopFunc(ic))
|
||||
require.Equal(t, 1, ic.VM.Estack().Len())
|
||||
require.Equal(t, result, hex.EncodeToString(ic.VM.Estack().Pop().Bytes()))
|
||||
}
|
||||
t.Run("raw bytes", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.Estack().PushVal(bs)
|
||||
checkGood(t, ic)
|
||||
})
|
||||
t.Run("interop", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.Estack().PushVal(stackitem.NewInterop(testVerifiable(bs)))
|
||||
checkGood(t, ic)
|
||||
})
|
||||
t.Run("container", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New(), Container: testVerifiable(bs)}
|
||||
ic.VM.Estack().PushVal(stackitem.Null{})
|
||||
checkGood(t, ic)
|
||||
})
|
||||
})
|
||||
t.Run("bad message", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.Estack().PushVal(stackitem.NewArray(nil))
|
||||
require.Error(t, interopFunc(ic))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSHA256(t *testing.T) {
|
||||
// 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254
|
||||
res := "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254"
|
||||
buf := io.NewBufBinWriter()
|
||||
emit.Bytes(buf.BinWriter, []byte{1, 0})
|
||||
emit.Syscall(buf.BinWriter, interopnames.NeoCryptoSHA256)
|
||||
prog := buf.Bytes()
|
||||
ic := &interop.Context{Trigger: trigger.Verification}
|
||||
Register(ic)
|
||||
v := ic.SpawnVM()
|
||||
v.Load(prog)
|
||||
require.NoError(t, v.Run())
|
||||
assert.Equal(t, 1, v.Estack().Len())
|
||||
assert.Equal(t, res, hex.EncodeToString(v.Estack().Pop().Bytes()))
|
||||
testHash0100(t, res, Sha256)
|
||||
}
|
||||
|
||||
func TestRIPEMD160(t *testing.T) {
|
||||
// 0x0100 hashes to 213492c0c6fc5d61497cf17249dd31cd9964b8a3
|
||||
res := "213492c0c6fc5d61497cf17249dd31cd9964b8a3"
|
||||
buf := io.NewBufBinWriter()
|
||||
emit.Bytes(buf.BinWriter, []byte{1, 0})
|
||||
emit.Syscall(buf.BinWriter, interopnames.NeoCryptoRIPEMD160)
|
||||
prog := buf.Bytes()
|
||||
ic := &interop.Context{Trigger: trigger.Verification}
|
||||
Register(ic)
|
||||
v := ic.SpawnVM()
|
||||
v.Load(prog)
|
||||
require.NoError(t, v.Run())
|
||||
assert.Equal(t, 1, v.Estack().Len())
|
||||
assert.Equal(t, res, hex.EncodeToString(v.Estack().Pop().Bytes()))
|
||||
testHash0100(t, res, RipeMD160)
|
||||
}
|
||||
|
|
35
pkg/core/interop/enumerator/interop_test.go
Normal file
35
pkg/core/interop/enumerator/interop_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package enumerator
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Enumerator is thoroughly tested in VM package, these are smoke tests.
|
||||
func TestEnumerator(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
full := []byte{4, 8, 15}
|
||||
ic.VM.Estack().PushVal(full[2:])
|
||||
require.NoError(t, Create(ic))
|
||||
ic.VM.Estack().PushVal(full[:2])
|
||||
require.NoError(t, Create(ic))
|
||||
require.NoError(t, Concat(ic))
|
||||
|
||||
res := ic.VM.Estack().Pop().Item()
|
||||
for i := range full {
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, Next(ic))
|
||||
require.True(t, ic.VM.Estack().Pop().Bool())
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, Value(ic))
|
||||
require.Equal(t, big.NewInt(int64(full[i])), ic.VM.Estack().Pop().BigInt())
|
||||
}
|
||||
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, Next(ic))
|
||||
require.False(t, ic.VM.Estack().Pop().Bool())
|
||||
}
|
48
pkg/core/interop/iterator/interop_test.go
Normal file
48
pkg/core/interop/iterator/interop_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package iterator
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Iterator is thoroughly tested in VM package, these are smoke tests.
|
||||
func TestIterator(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
full := []byte{4, 8, 15}
|
||||
ic.VM.Estack().PushVal(full[2:])
|
||||
require.NoError(t, Create(ic))
|
||||
ic.VM.Estack().PushVal(full[:2])
|
||||
require.NoError(t, Create(ic))
|
||||
require.NoError(t, Concat(ic))
|
||||
|
||||
res := ic.VM.Estack().Pop().Item()
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, vm.EnumeratorNext(ic.VM))
|
||||
require.True(t, ic.VM.Estack().Pop().Bool())
|
||||
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, Key(ic))
|
||||
require.Equal(t, big.NewInt(0), ic.VM.Estack().Pop().BigInt())
|
||||
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, vm.EnumeratorValue(ic.VM))
|
||||
require.Equal(t, big.NewInt(int64(full[0])), ic.VM.Estack().Pop().BigInt())
|
||||
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, vm.EnumeratorNext(ic.VM))
|
||||
require.True(t, ic.VM.Estack().Pop().Bool())
|
||||
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, Keys(ic))
|
||||
require.NoError(t, vm.EnumeratorValue(ic.VM))
|
||||
require.Equal(t, big.NewInt(1), ic.VM.Estack().Pop().BigInt())
|
||||
|
||||
ic.VM.Estack().PushVal(res)
|
||||
require.NoError(t, Values(ic))
|
||||
require.NoError(t, vm.EnumeratorValue(ic.VM))
|
||||
require.Equal(t, big.NewInt(int64(full[1])), ic.VM.Estack().Pop().BigInt())
|
||||
}
|
93
pkg/core/interop/runtime/engine.go
Normal file
93
pkg/core/interop/runtime/engine.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxEventNameLen is the maximum length of a name for event.
|
||||
MaxEventNameLen = 32
|
||||
// MaxNotificationSize is the maximum length of a runtime log message.
|
||||
MaxNotificationSize = 1024
|
||||
)
|
||||
|
||||
// GetExecutingScriptHash returns executing script hash.
|
||||
func GetExecutingScriptHash(ic *interop.Context) error {
|
||||
return ic.VM.PushContextScriptHash(0)
|
||||
}
|
||||
|
||||
// GetCallingScriptHash returns calling script hash.
|
||||
func GetCallingScriptHash(ic *interop.Context) error {
|
||||
return ic.VM.PushContextScriptHash(1)
|
||||
}
|
||||
|
||||
// GetEntryScriptHash returns entry script hash.
|
||||
func GetEntryScriptHash(ic *interop.Context) error {
|
||||
return ic.VM.PushContextScriptHash(ic.VM.Istack().Len() - 1)
|
||||
}
|
||||
|
||||
// Platform returns the name of the platform.
|
||||
func Platform(ic *interop.Context) error {
|
||||
ic.VM.Estack().PushVal([]byte("NEO"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTrigger returns the script trigger.
|
||||
func GetTrigger(ic *interop.Context) error {
|
||||
ic.VM.Estack().PushVal(byte(ic.Trigger))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Notify should pass stack item to the notify plugin to handle it, but
|
||||
// in neo-go the only meaningful thing to do here is to log.
|
||||
func Notify(ic *interop.Context) error {
|
||||
name := ic.VM.Estack().Pop().String()
|
||||
if len(name) > MaxEventNameLen {
|
||||
return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
|
||||
}
|
||||
elem := ic.VM.Estack().Pop()
|
||||
args := elem.Array()
|
||||
// But it has to be serializable, otherwise we either have some broken
|
||||
// (recursive) structure inside or an interop item that can't be used
|
||||
// outside of the interop subsystem anyway.
|
||||
bytes, err := stackitem.SerializeItem(elem.Item())
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad notification: %w", err)
|
||||
}
|
||||
if len(bytes) > MaxNotificationSize {
|
||||
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
|
||||
}
|
||||
ne := state.NotificationEvent{
|
||||
ScriptHash: ic.VM.GetCurrentScriptHash(),
|
||||
Name: name,
|
||||
Item: stackitem.DeepCopy(stackitem.NewArray(args)).(*stackitem.Array),
|
||||
}
|
||||
ic.Notifications = append(ic.Notifications, ne)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Log logs the message passed.
|
||||
func Log(ic *interop.Context) error {
|
||||
state := ic.VM.Estack().Pop().String()
|
||||
if len(state) > MaxNotificationSize {
|
||||
return fmt.Errorf("message length shouldn't exceed %v", MaxNotificationSize)
|
||||
}
|
||||
msg := fmt.Sprintf("%q", state)
|
||||
ic.Log.Info("runtime log",
|
||||
zap.Stringer("script", ic.VM.GetCurrentScriptHash()),
|
||||
zap.String("logs", msg))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTime returns timestamp of the block being verified, or the latest
|
||||
// one in the blockchain if no block is given to Context.
|
||||
func GetTime(ic *interop.Context) error {
|
||||
header := ic.Block.Header()
|
||||
ic.VM.Estack().PushVal(header.Timestamp)
|
||||
return nil
|
||||
}
|
175
pkg/core/interop/runtime/engine_test.go
Normal file
175
pkg/core/interop/runtime/engine_test.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
func checkStack(t *testing.T, v *vm.VM, args ...interface{}) {
|
||||
require.Equal(t, len(args), v.Estack().Len())
|
||||
for i := range args {
|
||||
require.Equal(t, stackitem.Make(args[i]), v.Estack().Pop().Item(), "%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTrigger(t *testing.T) {
|
||||
triggers := []trigger.Type{trigger.Application, trigger.Verification}
|
||||
for _, tr := range triggers {
|
||||
ic := &interop.Context{Trigger: tr, VM: vm.New()}
|
||||
require.NoError(t, GetTrigger(ic))
|
||||
checkStack(t, ic.VM, int64(tr))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatform(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
require.NoError(t, Platform(ic))
|
||||
checkStack(t, ic.VM, "NEO")
|
||||
}
|
||||
|
||||
func TestGetTime(t *testing.T) {
|
||||
b := block.New(netmode.UnitTestNet, false)
|
||||
b.Timestamp = rand.Uint64()
|
||||
ic := &interop.Context{VM: vm.New(), Block: b}
|
||||
require.NoError(t, GetTime(ic))
|
||||
checkStack(t, ic.VM, new(big.Int).SetUint64(b.Timestamp))
|
||||
}
|
||||
|
||||
func TestGetScriptHash(t *testing.T) {
|
||||
scripts := []struct {
|
||||
s []byte
|
||||
h util.Uint160
|
||||
}{
|
||||
{[]byte{1, 2, 3, 4}, hash.Hash160([]byte{1, 2, 3, 4})},
|
||||
{[]byte{1, 2, 3}, util.Uint160{4, 8, 15, 16}},
|
||||
{[]byte{1, 2}, hash.Hash160([]byte{1, 2})},
|
||||
{[]byte{1}, hash.Hash160([]byte{1})},
|
||||
}
|
||||
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.LoadScriptWithFlags(scripts[0].s, smartcontract.All)
|
||||
require.NoError(t, GetEntryScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[0].h.BytesBE())
|
||||
require.NoError(t, GetCallingScriptHash(ic))
|
||||
checkStack(t, ic.VM, util.Uint160{}.BytesBE())
|
||||
require.NoError(t, GetExecutingScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[0].h.BytesBE())
|
||||
|
||||
ic.VM.LoadScriptWithHash(scripts[1].s, scripts[1].h, smartcontract.All)
|
||||
require.NoError(t, GetEntryScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[0].h.BytesBE())
|
||||
require.NoError(t, GetCallingScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[0].h.BytesBE())
|
||||
require.NoError(t, GetExecutingScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[1].h.BytesBE())
|
||||
|
||||
ic.VM.LoadScript(scripts[2].s)
|
||||
require.NoError(t, GetEntryScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[0].h.BytesBE())
|
||||
require.NoError(t, GetCallingScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[1].h.BytesBE())
|
||||
require.NoError(t, GetExecutingScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[2].h.BytesBE())
|
||||
|
||||
ic.VM.LoadScript(scripts[3].s)
|
||||
require.NoError(t, GetEntryScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[0].h.BytesBE())
|
||||
require.NoError(t, GetCallingScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[2].h.BytesBE())
|
||||
require.NoError(t, GetExecutingScriptHash(ic))
|
||||
checkStack(t, ic.VM, scripts[3].h.BytesBE())
|
||||
}
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
newL := func(l zapcore.Level) (*zap.Logger, *zaptest.Buffer) {
|
||||
enc := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
|
||||
w := &zaptest.Buffer{}
|
||||
zc := zapcore.NewCore(enc, w, l)
|
||||
return zap.New(zc, zap.ErrorOutput(w)), w
|
||||
}
|
||||
h := random.Uint160()
|
||||
|
||||
t.Run("big message", func(t *testing.T) {
|
||||
ic := &interop.Context{Log: zap.NewNop(), VM: vm.New()}
|
||||
ic.VM.LoadScriptWithHash([]byte{1}, h, smartcontract.All)
|
||||
ic.VM.Estack().PushVal(string(make([]byte, MaxNotificationSize+1)))
|
||||
require.Error(t, Log(ic))
|
||||
})
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
log, buf := newL(zapcore.InfoLevel)
|
||||
ic := &interop.Context{Log: log, VM: vm.New()}
|
||||
ic.VM.LoadScriptWithHash([]byte{1}, h, smartcontract.All)
|
||||
ic.VM.Estack().PushVal("hello")
|
||||
require.NoError(t, Log(ic))
|
||||
|
||||
ls := buf.Lines()
|
||||
require.Equal(t, 1, len(ls))
|
||||
|
||||
var logMsg map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal([]byte(ls[0]), &logMsg))
|
||||
require.Equal(t, "info", logMsg["level"])
|
||||
require.Equal(t, "runtime log", logMsg["msg"])
|
||||
require.Equal(t, h.StringBE(), logMsg["script"])
|
||||
require.Equal(t, `"hello"`, logMsg["logs"])
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotify(t *testing.T) {
|
||||
h := random.Uint160()
|
||||
newIC := func(name string, args interface{}) *interop.Context {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.LoadScriptWithHash([]byte{1}, h, smartcontract.NoneFlag)
|
||||
ic.VM.Estack().PushVal(args)
|
||||
ic.VM.Estack().PushVal(name)
|
||||
return ic
|
||||
}
|
||||
t.Run("big name", func(t *testing.T) {
|
||||
ic := newIC(string(make([]byte, MaxEventNameLen+1)), []byte{42})
|
||||
require.Error(t, Notify(ic))
|
||||
})
|
||||
t.Run("recursive struct", func(t *testing.T) {
|
||||
arr := stackitem.NewArray([]stackitem.Item{stackitem.Null{}})
|
||||
arr.Append(arr)
|
||||
ic := newIC("event", arr)
|
||||
require.Error(t, Notify(ic))
|
||||
})
|
||||
t.Run("big notification", func(t *testing.T) {
|
||||
bs := stackitem.NewByteArray(make([]byte, MaxNotificationSize+1))
|
||||
arr := stackitem.NewArray([]stackitem.Item{bs})
|
||||
ic := newIC("event", arr)
|
||||
require.Error(t, Notify(ic))
|
||||
})
|
||||
t.Run("good", func(t *testing.T) {
|
||||
arr := stackitem.NewArray([]stackitem.Item{stackitem.Make(42)})
|
||||
ic := newIC("good event", arr)
|
||||
require.NoError(t, Notify(ic))
|
||||
require.Equal(t, 1, len(ic.Notifications))
|
||||
|
||||
ev := ic.Notifications[0]
|
||||
require.Equal(t, "good event", ev.Name)
|
||||
require.Equal(t, h, ev.ScriptHash)
|
||||
require.Equal(t, arr, ev.Item)
|
||||
// Check deep copy.
|
||||
arr.Value().([]stackitem.Item)[0] = stackitem.Null{}
|
||||
require.NotEqual(t, arr, ev.Item)
|
||||
})
|
||||
}
|
116
pkg/core/interop/runtime/util_test.go
Normal file
116
pkg/core/interop/runtime/util_test.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGasLeft(t *testing.T) {
|
||||
t.Run("no limit", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.GasLimit = -1
|
||||
ic.VM.AddGas(58)
|
||||
require.NoError(t, GasLeft(ic))
|
||||
checkStack(t, ic.VM, -1)
|
||||
})
|
||||
t.Run("with limit", func(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
ic.VM.GasLimit = 100
|
||||
ic.VM.AddGas(58)
|
||||
require.NoError(t, GasLeft(ic))
|
||||
checkStack(t, ic.VM, 42)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuntimeGetNotifications(t *testing.T) {
|
||||
v := vm.New()
|
||||
ic := &interop.Context{
|
||||
VM: v,
|
||||
Notifications: []state.NotificationEvent{
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})},
|
||||
{ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})},
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("NoFilter", func(t *testing.T) {
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
require.NoError(t, GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, len(ic.Notifications), len(arr))
|
||||
for i := range arr {
|
||||
elem := arr[i].Value().([]stackitem.Item)
|
||||
require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[i].Name, name)
|
||||
require.Equal(t, ic.Notifications[i].Item, elem[2])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WithFilter", func(t *testing.T) {
|
||||
h := util.Uint160{2}.BytesBE()
|
||||
v.Estack().PushVal(h)
|
||||
require.NoError(t, GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, 1, len(arr))
|
||||
elem := arr[0].Value().([]stackitem.Item)
|
||||
require.Equal(t, h, elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[1].Name, name)
|
||||
require.Equal(t, ic.Notifications[1].Item, elem[2])
|
||||
})
|
||||
|
||||
t.Run("Bad", func(t *testing.T) {
|
||||
t.Run("not bytes", func(t *testing.T) {
|
||||
v.Estack().PushVal(stackitem.NewInterop(util.Uint160{1}))
|
||||
require.Error(t, GetNotifications(ic))
|
||||
})
|
||||
t.Run("not uint160", func(t *testing.T) {
|
||||
v.Estack().PushVal([]byte{1, 2, 3})
|
||||
require.Error(t, GetNotifications(ic))
|
||||
})
|
||||
t.Run("too many notifications", func(t *testing.T) {
|
||||
for i := 0; i <= vm.MaxStackSize; i++ {
|
||||
ic.Notifications = append(ic.Notifications, state.NotificationEvent{
|
||||
ScriptHash: util.Uint160{3},
|
||||
Name: "Event3",
|
||||
Item: stackitem.NewArray(nil),
|
||||
})
|
||||
}
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
require.Error(t, GetNotifications(ic))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuntimeGetInvocationCounter(t *testing.T) {
|
||||
ic := &interop.Context{VM: vm.New()}
|
||||
h := random.Uint160()
|
||||
ic.VM.Invocations[h] = 42
|
||||
|
||||
t.Run("No invocations", func(t *testing.T) {
|
||||
h1 := h
|
||||
h1[0] ^= 0xFF
|
||||
ic.VM.LoadScriptWithHash([]byte{1}, h1, smartcontract.NoneFlag)
|
||||
// do not return an error in this case.
|
||||
require.NoError(t, GetInvocationCounter(ic))
|
||||
checkStack(t, ic.VM, 1)
|
||||
})
|
||||
t.Run("NonZero", func(t *testing.T) {
|
||||
ic.VM.LoadScriptWithHash([]byte{1}, h, smartcontract.NoneFlag)
|
||||
require.NoError(t, GetInvocationCounter(ic))
|
||||
checkStack(t, ic.VM, 42)
|
||||
})
|
||||
}
|
|
@ -2,14 +2,12 @@ package core
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
|
@ -117,13 +115,13 @@ func contractCreate(ic *interop.Context) error {
|
|||
if contract != nil && err == nil {
|
||||
return errors.New("contract already exists")
|
||||
}
|
||||
if !manif.IsValid(h) {
|
||||
return errors.New("failed to check contract script hash against manifest")
|
||||
}
|
||||
id, err := ic.DAO.GetAndUpdateNextContractID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !manif.IsValid(h) {
|
||||
return errors.New("failed to check contract script hash against manifest")
|
||||
}
|
||||
newcontract := &state.Contract{
|
||||
ID: id,
|
||||
Hash: h,
|
||||
|
@ -193,51 +191,3 @@ func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeSerialize serializes top stack item into a ByteArray.
|
||||
func runtimeSerialize(ic *interop.Context) error {
|
||||
return vm.RuntimeSerialize(ic.VM)
|
||||
}
|
||||
|
||||
// runtimeDeserialize deserializes ByteArray from a stack into an item.
|
||||
func runtimeDeserialize(ic *interop.Context) error {
|
||||
return vm.RuntimeDeserialize(ic.VM)
|
||||
}
|
||||
|
||||
// runtimeEncodeBase64 encodes top stack item into a base64 string.
|
||||
func runtimeEncodeBase64(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().Bytes()
|
||||
result := base64.StdEncoding.EncodeToString(src)
|
||||
ic.VM.Estack().PushVal([]byte(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeDecodeBase64 decodes top stack item from base64 string to byte array.
|
||||
func runtimeDecodeBase64(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().String()
|
||||
result, err := base64.StdEncoding.DecodeString(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ic.VM.Estack().PushVal(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeEncodeBase58 encodes top stack item into a base58 string.
|
||||
func runtimeEncodeBase58(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().Bytes()
|
||||
result := base58.Encode(src)
|
||||
ic.VM.Estack().PushVal([]byte(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeDecodeBase58 decodes top stack item from base58 string to byte array.
|
||||
func runtimeDecodeBase58(ic *interop.Context) error {
|
||||
src := ic.VM.Estack().Pop().String()
|
||||
result, err := base58.Decode(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ic.VM.Estack().PushVal(result)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"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/util"
|
||||
|
@ -37,12 +32,6 @@ import (
|
|||
* TestRuntimeDeserialize
|
||||
*/
|
||||
|
||||
func TestGetTrigger(t *testing.T) {
|
||||
_, _, context, chain := createVMAndPushBlock(t)
|
||||
defer chain.Close()
|
||||
require.NoError(t, runtimeGetTrigger(context))
|
||||
}
|
||||
|
||||
func TestStorageFind(t *testing.T) {
|
||||
v, contractState, context, chain := createVMAndContractState(t)
|
||||
defer chain.Close()
|
||||
|
@ -137,130 +126,6 @@ func TestStorageFind(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestECDSAVerify(t *testing.T) {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
chain := newTestChain(t)
|
||||
defer chain.Close()
|
||||
|
||||
ic := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), nil, nil)
|
||||
runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) {
|
||||
ic.SpawnVM()
|
||||
for i := range args {
|
||||
ic.VM.Estack().PushVal(args[i])
|
||||
}
|
||||
|
||||
var err error
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic: %v", r)
|
||||
}
|
||||
}()
|
||||
err = crypto.ECDSASecp256r1Verify(ic)
|
||||
}()
|
||||
|
||||
if isErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, ic.VM.Estack().Len())
|
||||
require.Equal(t, result, ic.VM.Estack().Pop().Value().(bool))
|
||||
}
|
||||
|
||||
msg := []byte("test message")
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, false, true, sign, priv.PublicKey().Bytes(), msg)
|
||||
})
|
||||
|
||||
t.Run("signed interop item", func(t *testing.T) {
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1)
|
||||
msg := tx.GetSignedPart()
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.NewInterop(tx))
|
||||
})
|
||||
|
||||
t.Run("signed script container", func(t *testing.T) {
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1)
|
||||
msg := tx.GetSignedPart()
|
||||
sign := priv.Sign(msg)
|
||||
ic.Container = tx
|
||||
runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.Null{})
|
||||
})
|
||||
|
||||
t.Run("missing arguments", func(t *testing.T) {
|
||||
runCase(t, true, false)
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, true, false, sign)
|
||||
runCase(t, true, false, sign, priv.PublicKey().Bytes())
|
||||
})
|
||||
|
||||
t.Run("invalid signature", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
sign[0] = ^sign[0]
|
||||
runCase(t, false, false, sign, priv.PublicKey().Bytes(), msg)
|
||||
})
|
||||
|
||||
t.Run("invalid public key", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
pub := priv.PublicKey().Bytes()
|
||||
pub[0] = 0xFF // invalid prefix
|
||||
runCase(t, true, false, sign, pub, msg)
|
||||
})
|
||||
|
||||
t.Run("invalid message", func(t *testing.T) {
|
||||
sign := priv.Sign(msg)
|
||||
runCase(t, true, false, sign, priv.PublicKey().Bytes(),
|
||||
stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(msg)}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuntimeEncodeDecode(t *testing.T) {
|
||||
original := []byte("my pretty string")
|
||||
encoded64 := base64.StdEncoding.EncodeToString(original)
|
||||
encoded58 := base58.Encode(original)
|
||||
v, ic, bc := createVM(t)
|
||||
defer bc.Close()
|
||||
|
||||
t.Run("Encode64", func(t *testing.T) {
|
||||
v.Estack().PushVal(original)
|
||||
require.NoError(t, runtimeEncodeBase64(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, []byte(encoded64), actual)
|
||||
})
|
||||
|
||||
t.Run("Encode58", func(t *testing.T) {
|
||||
v.Estack().PushVal(original)
|
||||
require.NoError(t, runtimeEncodeBase58(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, []byte(encoded58), actual)
|
||||
})
|
||||
t.Run("Decode64/positive", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded64)
|
||||
require.NoError(t, runtimeDecodeBase64(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, original, actual)
|
||||
})
|
||||
t.Run("Decode64/error", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded64 + "%")
|
||||
require.Error(t, runtimeDecodeBase64(ic))
|
||||
})
|
||||
t.Run("Decode58/positive", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded58)
|
||||
require.NoError(t, runtimeDecodeBase58(ic))
|
||||
actual := v.Estack().Pop().Bytes()
|
||||
require.Equal(t, original, actual)
|
||||
})
|
||||
t.Run("Decode58/error", func(t *testing.T) {
|
||||
v.Estack().PushVal(encoded58 + "%")
|
||||
require.Error(t, runtimeDecodeBase58(ic))
|
||||
})
|
||||
}
|
||||
|
||||
// Helper functions to create VM, InteropContext, TX, Account, Contract.
|
||||
|
||||
func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -27,10 +26,6 @@ const (
|
|||
// MaxStorageValueLen is the maximum length of a value for storage items.
|
||||
// It is set to be the maximum value for uint16.
|
||||
MaxStorageValueLen = 65535
|
||||
// MaxEventNameLen is the maximum length of a name for event.
|
||||
MaxEventNameLen = 32
|
||||
// MaxNotificationSize is the maximum length of a runtime log message.
|
||||
MaxNotificationSize = 1024
|
||||
)
|
||||
|
||||
// StorageContext contains storing id and read/write flag, it's used as
|
||||
|
@ -231,82 +226,6 @@ func engineGetScriptContainer(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// engineGetExecutingScriptHash returns executing script hash.
|
||||
func engineGetExecutingScriptHash(ic *interop.Context) error {
|
||||
return ic.VM.PushContextScriptHash(0)
|
||||
}
|
||||
|
||||
// engineGetCallingScriptHash returns calling script hash.
|
||||
func engineGetCallingScriptHash(ic *interop.Context) error {
|
||||
return ic.VM.PushContextScriptHash(1)
|
||||
}
|
||||
|
||||
// engineGetEntryScriptHash returns entry script hash.
|
||||
func engineGetEntryScriptHash(ic *interop.Context) error {
|
||||
return ic.VM.PushContextScriptHash(ic.VM.Istack().Len() - 1)
|
||||
}
|
||||
|
||||
// runtimePlatform returns the name of the platform.
|
||||
func runtimePlatform(ic *interop.Context) error {
|
||||
ic.VM.Estack().PushVal([]byte("NEO"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeGetTrigger returns the script trigger.
|
||||
func runtimeGetTrigger(ic *interop.Context) error {
|
||||
ic.VM.Estack().PushVal(byte(ic.Trigger))
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeNotify should pass stack item to the notify plugin to handle it, but
|
||||
// in neo-go the only meaningful thing to do here is to log.
|
||||
func runtimeNotify(ic *interop.Context) error {
|
||||
name := ic.VM.Estack().Pop().String()
|
||||
if len(name) > MaxEventNameLen {
|
||||
return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
|
||||
}
|
||||
elem := ic.VM.Estack().Pop()
|
||||
args := elem.Array()
|
||||
// But it has to be serializable, otherwise we either have some broken
|
||||
// (recursive) structure inside or an interop item that can't be used
|
||||
// outside of the interop subsystem anyway.
|
||||
bytes, err := stackitem.SerializeItem(elem.Item())
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad notification: %w", err)
|
||||
}
|
||||
if len(bytes) > MaxNotificationSize {
|
||||
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
|
||||
}
|
||||
ne := state.NotificationEvent{
|
||||
ScriptHash: ic.VM.GetCurrentScriptHash(),
|
||||
Name: name,
|
||||
Item: stackitem.DeepCopy(stackitem.NewArray(args)).(*stackitem.Array),
|
||||
}
|
||||
ic.Notifications = append(ic.Notifications, ne)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeLog logs the message passed.
|
||||
func runtimeLog(ic *interop.Context) error {
|
||||
state := ic.VM.Estack().Pop().String()
|
||||
if len(state) > MaxNotificationSize {
|
||||
return fmt.Errorf("message length shouldn't exceed %v", MaxNotificationSize)
|
||||
}
|
||||
msg := fmt.Sprintf("%q", state)
|
||||
ic.Log.Info("runtime log",
|
||||
zap.Stringer("script", ic.VM.GetCurrentScriptHash()),
|
||||
zap.String("logs", msg))
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeGetTime returns timestamp of the block being verified, or the latest
|
||||
// one in the blockchain if no block is given to Context.
|
||||
func runtimeGetTime(ic *interop.Context) error {
|
||||
header := ic.Block.Header()
|
||||
ic.VM.Estack().PushVal(header.Timestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageDelete deletes stored key-value pair.
|
||||
func storageDelete(ic *interop.Context) error {
|
||||
stcInterface := ic.VM.Estack().Pop().Value()
|
||||
|
|
|
@ -31,6 +31,28 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBCGetTransactionHeight(t *testing.T) {
|
||||
v, tx, ic, chain := createVMAndTX(t)
|
||||
defer chain.Close()
|
||||
|
||||
for i := 0; i < 13; i++ {
|
||||
require.NoError(t, chain.AddBlock(chain.newBlock()))
|
||||
}
|
||||
require.NoError(t, ic.DAO.StoreAsTransaction(tx, 13, nil))
|
||||
t.Run("good", func(t *testing.T) {
|
||||
v.Estack().PushVal(tx.Hash().BytesBE())
|
||||
require.NoError(t, bcGetTransactionHeight(ic))
|
||||
require.Equal(t, big.NewInt(13), v.Estack().Pop().BigInt())
|
||||
})
|
||||
t.Run("bad", func(t *testing.T) {
|
||||
h := tx.Hash()
|
||||
h[0] ^= 0xFF
|
||||
v.Estack().PushVal(h.BytesBE())
|
||||
require.NoError(t, bcGetTransactionHeight(ic))
|
||||
require.Equal(t, big.NewInt(-1), v.Estack().Pop().BigInt())
|
||||
})
|
||||
}
|
||||
|
||||
func TestBCGetTransaction(t *testing.T) {
|
||||
v, tx, context, chain := createVMAndTX(t)
|
||||
defer chain.Close()
|
||||
|
@ -244,77 +266,6 @@ func TestContractCreateAccount(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestRuntimeGasLeft(t *testing.T) {
|
||||
v, ic, chain := createVM(t)
|
||||
defer chain.Close()
|
||||
|
||||
v.GasLimit = 100
|
||||
v.AddGas(58)
|
||||
require.NoError(t, runtime.GasLeft(ic))
|
||||
require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64())
|
||||
}
|
||||
|
||||
func TestRuntimeGetNotifications(t *testing.T) {
|
||||
v, ic, chain := createVM(t)
|
||||
defer chain.Close()
|
||||
|
||||
ic.Notifications = []state.NotificationEvent{
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})},
|
||||
{ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})},
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})},
|
||||
}
|
||||
|
||||
t.Run("NoFilter", func(t *testing.T) {
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
require.NoError(t, runtime.GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, len(ic.Notifications), len(arr))
|
||||
for i := range arr {
|
||||
elem := arr[i].Value().([]stackitem.Item)
|
||||
require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[i].Name, name)
|
||||
require.Equal(t, ic.Notifications[i].Item, elem[2])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("WithFilter", func(t *testing.T) {
|
||||
h := util.Uint160{2}.BytesBE()
|
||||
v.Estack().PushVal(h)
|
||||
require.NoError(t, runtime.GetNotifications(ic))
|
||||
|
||||
arr := v.Estack().Pop().Array()
|
||||
require.Equal(t, 1, len(arr))
|
||||
elem := arr[0].Value().([]stackitem.Item)
|
||||
require.Equal(t, h, elem[0].Value())
|
||||
name, err := stackitem.ToString(elem[1])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ic.Notifications[1].Name, name)
|
||||
require.Equal(t, ic.Notifications[1].Item, elem[2])
|
||||
})
|
||||
}
|
||||
|
||||
func TestRuntimeGetInvocationCounter(t *testing.T) {
|
||||
v, ic, chain := createVM(t)
|
||||
defer chain.Close()
|
||||
|
||||
ic.VM.Invocations[hash.Hash160([]byte{2})] = 42
|
||||
|
||||
t.Run("No invocations", func(t *testing.T) {
|
||||
v.LoadScript([]byte{1})
|
||||
// do not return an error in this case.
|
||||
require.NoError(t, runtime.GetInvocationCounter(ic))
|
||||
require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
t.Run("NonZero", func(t *testing.T) {
|
||||
v.LoadScript([]byte{2})
|
||||
require.NoError(t, runtime.GetInvocationCounter(ic))
|
||||
require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64())
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockchainGetContractState(t *testing.T) {
|
||||
v, cs, ic, bc := createVMAndContractState(t)
|
||||
defer bc.Close()
|
||||
|
@ -372,6 +323,83 @@ func TestStoragePut(t *testing.T) {
|
|||
initVM(t, []byte{4}, []byte{5, 6}, StoragePrice)
|
||||
require.NoError(t, storagePut(ic))
|
||||
})
|
||||
|
||||
t.Run("check limits", func(t *testing.T) {
|
||||
initVM(t, make([]byte, MaxStorageKeyLen), make([]byte, MaxStorageValueLen), -1)
|
||||
require.NoError(t, storagePut(ic))
|
||||
})
|
||||
|
||||
t.Run("bad", func(t *testing.T) {
|
||||
t.Run("readonly context", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, []byte{1}, -1)
|
||||
require.NoError(t, storageContextAsReadOnly(ic))
|
||||
require.Error(t, storagePut(ic))
|
||||
})
|
||||
t.Run("big key", func(t *testing.T) {
|
||||
initVM(t, make([]byte, MaxStorageKeyLen+1), []byte{1}, -1)
|
||||
require.Error(t, storagePut(ic))
|
||||
})
|
||||
t.Run("big value", func(t *testing.T) {
|
||||
initVM(t, []byte{1}, make([]byte, MaxStorageValueLen+1), -1)
|
||||
require.Error(t, storagePut(ic))
|
||||
})
|
||||
t.Run("item exists and is const", func(t *testing.T) {
|
||||
v := ic.SpawnVM()
|
||||
v.LoadScript(cs.Script)
|
||||
v.GasLimit = -1
|
||||
v.Estack().PushVal(1)
|
||||
v.Estack().PushVal("value")
|
||||
v.Estack().PushVal("key")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storagePutEx(ic))
|
||||
|
||||
v.Estack().PushVal("new")
|
||||
v.Estack().PushVal("key")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.Error(t, storagePut(ic))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStorageDelete(t *testing.T) {
|
||||
v, cs, ic, bc := createVMAndContractState(t)
|
||||
defer bc.Close()
|
||||
|
||||
require.NoError(t, ic.DAO.PutContractState(cs))
|
||||
v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All)
|
||||
put := func(key, value string, flag int) {
|
||||
v.Estack().PushVal(flag)
|
||||
v.Estack().PushVal(value)
|
||||
v.Estack().PushVal(key)
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storagePutEx(ic))
|
||||
}
|
||||
put("key1", "value1", 0)
|
||||
put("key2", "value2", 0)
|
||||
put("key3", "value3", 0)
|
||||
put("key4", "value4", 1)
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
v.Estack().PushVal("key1")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storageDelete(ic))
|
||||
})
|
||||
t.Run("readonly context", func(t *testing.T) {
|
||||
v.Estack().PushVal("key2")
|
||||
require.NoError(t, storageGetReadOnlyContext(ic))
|
||||
require.Error(t, storageDelete(ic))
|
||||
})
|
||||
t.Run("readonly context (from normal)", func(t *testing.T) {
|
||||
v.Estack().PushVal("key3")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storageContextAsReadOnly(ic))
|
||||
require.Error(t, storageDelete(ic))
|
||||
})
|
||||
t.Run("constant item", func(t *testing.T) {
|
||||
v.Estack().PushVal("key4")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.Error(t, storageDelete(ic))
|
||||
})
|
||||
}
|
||||
|
||||
// getTestContractState returns 2 contracts second of which is allowed to call the first.
|
||||
|
@ -660,14 +688,24 @@ func TestContractCreate(t *testing.T) {
|
|||
|
||||
// nef.NewFile() cares about version a lot.
|
||||
config.Version = "0.90.0-test"
|
||||
|
||||
ne, err := nef.NewFile(cs.Script)
|
||||
require.NoError(t, err)
|
||||
neb, err := ne.Bytes()
|
||||
require.NoError(t, err)
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
sender := util.Uint160{1, 2, 3}
|
||||
h := state.CreateContractHash(sender, ne.Script)
|
||||
sig := priv.Sign(h.BytesBE())
|
||||
cs.Manifest.Groups = []manifest.Group{{
|
||||
PublicKey: priv.PublicKey(),
|
||||
Signature: sig,
|
||||
}}
|
||||
m, err := json.Marshal(cs.Manifest)
|
||||
require.NoError(t, err)
|
||||
putArgsOnStack := func() {
|
||||
manifest, err := json.Marshal(cs.Manifest)
|
||||
require.NoError(t, err)
|
||||
ne, err := nef.NewFile(cs.Script)
|
||||
require.NoError(t, err)
|
||||
neb, err := ne.Bytes()
|
||||
require.NoError(t, err)
|
||||
v.Estack().PushVal(manifest)
|
||||
v.Estack().PushVal(m)
|
||||
v.Estack().PushVal(neb)
|
||||
}
|
||||
|
||||
|
@ -678,11 +716,36 @@ func TestContractCreate(t *testing.T) {
|
|||
})
|
||||
|
||||
ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0)
|
||||
var sender = util.Uint160{1, 2, 3}
|
||||
ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender})
|
||||
cs.ID = 0
|
||||
cs.Hash = state.CreateContractHash(sender, cs.Script)
|
||||
|
||||
t.Run("missing NEF", func(t *testing.T) {
|
||||
v.Estack().PushVal(m)
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
require.Error(t, contractCreate(ic))
|
||||
})
|
||||
t.Run("missing manifest", func(t *testing.T) {
|
||||
v.Estack().PushVal(stackitem.Null{})
|
||||
v.Estack().PushVal(neb)
|
||||
require.Error(t, contractCreate(ic))
|
||||
})
|
||||
t.Run("invalid manifest (empty)", func(t *testing.T) {
|
||||
v.Estack().PushVal([]byte{})
|
||||
v.Estack().PushVal(neb)
|
||||
require.Error(t, contractCreate(ic))
|
||||
})
|
||||
|
||||
t.Run("invalid manifest (group signature)", func(t *testing.T) {
|
||||
cs.Manifest.Groups[0].Signature = make([]byte, 11)
|
||||
rawManif, err := json.Marshal(cs.Manifest)
|
||||
require.NoError(t, err)
|
||||
v.Estack().PushVal(rawManif)
|
||||
v.Estack().PushVal(neb)
|
||||
require.Error(t, contractCreate(ic))
|
||||
})
|
||||
|
||||
cs.Manifest.Groups[0].Signature = sig
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
putArgsOnStack()
|
||||
|
||||
|
@ -862,6 +925,24 @@ func TestContractUpdate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestContractDestroy(t *testing.T) {
|
||||
v, cs, ic, bc := createVMAndContractState(t)
|
||||
defer bc.Close()
|
||||
|
||||
v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All)
|
||||
require.NoError(t, contractDestroy(ic)) // silent error when contract is missing
|
||||
require.NoError(t, ic.DAO.PutContractState(cs))
|
||||
|
||||
v.Estack().PushVal("value")
|
||||
v.Estack().PushVal("key")
|
||||
require.NoError(t, storageGetContext(ic))
|
||||
require.NoError(t, storagePut(ic))
|
||||
require.NotNil(t, ic.DAO.GetStorageItem(cs.ID, []byte("key")))
|
||||
require.NoError(t, contractDestroy(ic))
|
||||
require.Nil(t, ic.DAO.GetStorageItem(cs.ID, []byte("key")))
|
||||
require.Error(t, storageGetContext(ic))
|
||||
}
|
||||
|
||||
// TestContractCreateDeploy checks that `_deploy` method was called
|
||||
// during contract creation or update.
|
||||
func TestContractCreateDeploy(t *testing.T) {
|
||||
|
|
|
@ -34,13 +34,13 @@ func SpawnVM(ic *interop.Context) *vm.VM {
|
|||
// All lists are sorted, keep 'em this way, please.
|
||||
var systemInterops = []interop.Function{
|
||||
{Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 100000, ParamCount: 2},
|
||||
{Name: interopnames.SystemBinaryBase58Decode, Func: runtimeDecodeBase58, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryBase58Encode, Func: runtimeEncodeBase58, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryBase64Decode, Func: runtimeDecodeBase64, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryBase64Encode, Func: runtimeEncodeBase64, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryDeserialize, Func: runtimeDeserialize, Price: 500000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 500000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 100000, ParamCount: 2},
|
||||
{Name: interopnames.SystemBinarySerialize, Func: runtimeSerialize, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 100000, ParamCount: 1},
|
||||
{Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000,
|
||||
RequiredFlags: smartcontract.AllowStates, ParamCount: 1},
|
||||
{Name: interopnames.SystemBlockchainGetContract, Func: bcGetContract, Price: 1000000,
|
||||
|
@ -83,19 +83,19 @@ var systemInterops = []interop.Function{
|
|||
{Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 30000,
|
||||
RequiredFlags: smartcontract.NoneFlag, ParamCount: 1},
|
||||
{Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: engineGetCallingScriptHash, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: engineGetEntryScriptHash, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetExecutingScriptHash, Func: engineGetExecutingScriptHash, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: runtime.GetCallingScriptHash, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: runtime.GetEntryScriptHash, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetExecutingScriptHash, Func: runtime.GetExecutingScriptHash, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetInvocationCounter, Func: runtime.GetInvocationCounter, Price: 400},
|
||||
{Name: interopnames.SystemRuntimeGetNotifications, Func: runtime.GetNotifications, Price: 10000, ParamCount: 1},
|
||||
{Name: interopnames.SystemRuntimeGetScriptContainer, Func: engineGetScriptContainer, Price: 250},
|
||||
{Name: interopnames.SystemRuntimeGetTime, Func: runtimeGetTime, Price: 250, RequiredFlags: smartcontract.AllowStates},
|
||||
{Name: interopnames.SystemRuntimeGetTrigger, Func: runtimeGetTrigger, Price: 250},
|
||||
{Name: interopnames.SystemRuntimeLog, Func: runtimeLog, Price: 1000000, RequiredFlags: smartcontract.AllowNotify,
|
||||
{Name: interopnames.SystemRuntimeGetTime, Func: runtime.GetTime, Price: 250, RequiredFlags: smartcontract.AllowStates},
|
||||
{Name: interopnames.SystemRuntimeGetTrigger, Func: runtime.GetTrigger, Price: 250},
|
||||
{Name: interopnames.SystemRuntimeLog, Func: runtime.Log, Price: 1000000, RequiredFlags: smartcontract.AllowNotify,
|
||||
ParamCount: 1, DisallowCallback: true},
|
||||
{Name: interopnames.SystemRuntimeNotify, Func: runtimeNotify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify,
|
||||
{Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify,
|
||||
ParamCount: 2, DisallowCallback: true},
|
||||
{Name: interopnames.SystemRuntimePlatform, Func: runtimePlatform, Price: 250},
|
||||
{Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 250},
|
||||
{Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: StoragePrice,
|
||||
RequiredFlags: smartcontract.AllowModifyStates, ParamCount: 2, DisallowCallback: true},
|
||||
{Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1000000, RequiredFlags: smartcontract.AllowStates,
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/rfc6979"
|
||||
)
|
||||
|
@ -20,16 +21,26 @@ type PrivateKey struct {
|
|||
ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// NewPrivateKey creates a new random Secp256k1 private key.
|
||||
// NewPrivateKey creates a new random Secp256r1 private key.
|
||||
func NewPrivateKey() (*PrivateKey, error) {
|
||||
priv, x, y, err := elliptic.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
return newPrivateKeyOnCurve(elliptic.P256())
|
||||
}
|
||||
|
||||
// NewSecp256k1PrivateKey creates a new random Secp256k1 private key.
|
||||
func NewSecp256k1PrivateKey() (*PrivateKey, error) {
|
||||
return newPrivateKeyOnCurve(btcec.S256())
|
||||
}
|
||||
|
||||
// newPrivateKeyOnCurve creates a new random private key using curve c.
|
||||
func newPrivateKeyOnCurve(c elliptic.Curve) (*PrivateKey, error) {
|
||||
priv, x, y, err := elliptic.GenerateKey(c, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PrivateKey{
|
||||
ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: elliptic.P256(),
|
||||
Curve: c,
|
||||
X: x,
|
||||
Y: y,
|
||||
},
|
||||
|
|
|
@ -6,7 +6,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/keytestcases"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrivateKey(t *testing.T) {
|
||||
|
@ -28,6 +30,21 @@ func TestPrivateKey(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewPrivateKeyOnCurve(t *testing.T) {
|
||||
msg := []byte{1, 2, 3}
|
||||
h := hash.Sha256(msg).BytesBE()
|
||||
t.Run("Secp256r1", func(t *testing.T) {
|
||||
p, err := NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
p.PublicKey().Verify(p.Sign(msg), h)
|
||||
})
|
||||
t.Run("Secp256k1", func(t *testing.T) {
|
||||
p, err := NewSecp256k1PrivateKey()
|
||||
require.NoError(t, err)
|
||||
p.PublicKey().Verify(p.Sign(msg), h)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrivateKeyFromWIF(t *testing.T) {
|
||||
for _, testCase := range keytestcases.Arr {
|
||||
key, err := NewPrivateKeyFromWIF(testCase.Wif)
|
||||
|
|
|
@ -528,11 +528,21 @@ func testIterableCreate(t *testing.T, typ string, isByteArray bool) {
|
|||
func TestEnumeratorCreate(t *testing.T) {
|
||||
t.Run("Array", func(t *testing.T) { testIterableCreate(t, "Enumerator", false) })
|
||||
t.Run("ByteArray", func(t *testing.T) { testIterableCreate(t, "Enumerator", true) })
|
||||
t.Run("Interop", func(t *testing.T) {
|
||||
v := New()
|
||||
v.Estack().PushVal(stackitem.NewInterop([]byte{42}))
|
||||
require.Error(t, EnumeratorCreate(v))
|
||||
})
|
||||
}
|
||||
|
||||
func TestIteratorCreate(t *testing.T) {
|
||||
t.Run("Array", func(t *testing.T) { testIterableCreate(t, "Iterator", false) })
|
||||
t.Run("ByteArray", func(t *testing.T) { testIterableCreate(t, "Iterator", true) })
|
||||
t.Run("Interop", func(t *testing.T) {
|
||||
v := New()
|
||||
v.Estack().PushVal(stackitem.NewInterop([]byte{42}))
|
||||
require.Error(t, IteratorCreate(v))
|
||||
})
|
||||
}
|
||||
|
||||
func testIterableConcat(t *testing.T, typ string) {
|
||||
|
|
Loading…
Reference in a new issue