mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 03:47:18 +00:00
1b83dc2476
Mostly it's about Go 1.22+ syntax with ranging over integers, but it also prefers ranging over slices where possible (it makes code a little better to read). Notice that we have a number of dangerous loops where slices are mutated during loop execution, many of these can't be converted since we need proper length evalutation at every iteration. Signed-off-by: Roman Khimov <roman@nspcc.ru>
282 lines
7.7 KiB
Go
282 lines
7.7 KiB
Go
package vm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func testSignatureContract() []byte {
|
|
prog := make([]byte, 40)
|
|
prog[0] = byte(opcode.PUSHDATA1)
|
|
prog[1] = 33
|
|
prog[35] = byte(opcode.SYSCALL)
|
|
binary.LittleEndian.PutUint32(prog[36:], verifyInteropID)
|
|
return prog
|
|
}
|
|
|
|
func TestParseSignatureContract(t *testing.T) {
|
|
prog := testSignatureContract()
|
|
pub := randomBytes(33)
|
|
copy(prog[2:], pub)
|
|
actual, ok := ParseSignatureContract(prog)
|
|
require.True(t, ok)
|
|
require.Equal(t, pub, actual)
|
|
}
|
|
|
|
func TestIsSignatureContract(t *testing.T) {
|
|
t.Run("valid contract", func(t *testing.T) {
|
|
prog := testSignatureContract()
|
|
assert.True(t, IsSignatureContract(prog))
|
|
assert.True(t, IsStandardContract(prog))
|
|
})
|
|
|
|
t.Run("invalid interop ID", func(t *testing.T) {
|
|
prog := testSignatureContract()
|
|
binary.LittleEndian.PutUint32(prog[36:], ^verifyInteropID)
|
|
assert.False(t, IsSignatureContract(prog))
|
|
assert.False(t, IsStandardContract(prog))
|
|
})
|
|
|
|
t.Run("invalid pubkey size", func(t *testing.T) {
|
|
prog := testSignatureContract()
|
|
prog[1] = 32
|
|
assert.False(t, IsSignatureContract(prog))
|
|
assert.False(t, IsStandardContract(prog))
|
|
})
|
|
|
|
t.Run("invalid length", func(t *testing.T) {
|
|
prog := testSignatureContract()
|
|
prog = append(prog, 0)
|
|
assert.False(t, IsSignatureContract(prog))
|
|
assert.False(t, IsStandardContract(prog))
|
|
})
|
|
}
|
|
|
|
func testMultisigContract(t *testing.T, n, m int) []byte {
|
|
pubs := make(keys.PublicKeys, n)
|
|
for i := range n {
|
|
priv, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
pubs[i] = priv.PublicKey()
|
|
}
|
|
|
|
prog, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
|
|
require.NoError(t, err)
|
|
return prog
|
|
}
|
|
|
|
func TestIsMultiSigContract(t *testing.T) {
|
|
t.Run("valid contract", func(t *testing.T) {
|
|
prog := testMultisigContract(t, 2, 2)
|
|
assert.True(t, IsMultiSigContract(prog))
|
|
assert.True(t, IsStandardContract(prog))
|
|
})
|
|
|
|
t.Run("0-length", func(t *testing.T) {
|
|
assert.False(t, IsMultiSigContract([]byte{}))
|
|
})
|
|
|
|
t.Run("invalid param", func(t *testing.T) {
|
|
prog := []byte{byte(opcode.PUSHDATA1), 10}
|
|
assert.False(t, IsMultiSigContract(prog))
|
|
})
|
|
|
|
t.Run("too many keys", func(t *testing.T) {
|
|
prog := testMultisigContract(t, 1025, 1)
|
|
assert.False(t, IsMultiSigContract(prog))
|
|
})
|
|
|
|
t.Run("invalid interop ID", func(t *testing.T) {
|
|
prog := testMultisigContract(t, 2, 2)
|
|
prog[len(prog)-4] ^= 0xFF
|
|
assert.False(t, IsMultiSigContract(prog))
|
|
})
|
|
|
|
t.Run("invalid keys number", func(t *testing.T) {
|
|
prog := testMultisigContract(t, 2, 2)
|
|
prog[len(prog)-6] = byte(opcode.PUSH3)
|
|
assert.False(t, IsMultiSigContract(prog))
|
|
})
|
|
|
|
t.Run("invalid length", func(t *testing.T) {
|
|
prog := testMultisigContract(t, 2, 2)
|
|
prog = append(prog, 0)
|
|
assert.False(t, IsMultiSigContract(prog))
|
|
})
|
|
}
|
|
|
|
func TestIsScriptCorrect(t *testing.T) {
|
|
w := io.NewBufBinWriter()
|
|
emit.String(w.BinWriter, "something")
|
|
|
|
jmpOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.JMP, opcode.Opcode(-jmpOff))
|
|
|
|
retOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
|
|
jmplOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.JMPL, opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff))
|
|
|
|
tryOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.TRY, opcode.Opcode(3), opcode.Opcode(0xfb)) // -5
|
|
|
|
trylOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.TRYL, opcode.Opcode(0xfd), opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff),
|
|
opcode.Opcode(9), opcode.Opcode(0), opcode.Opcode(0), opcode.Opcode(0))
|
|
|
|
istypeOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.ISTYPE, opcode.Opcode(stackitem.IntegerT))
|
|
|
|
pushOff := w.Len()
|
|
emit.String(w.BinWriter, "else")
|
|
|
|
good := w.Bytes()
|
|
|
|
t.Run("good", func(t *testing.T) {
|
|
require.NoError(t, IsScriptCorrect(good, nil))
|
|
})
|
|
|
|
t.Run("bad instruction", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[retOff] = 0xff
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("out of bounds JMP 1", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[jmpOff+1] = 0x80 // -128
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("out of bounds JMP 2", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[jmpOff+1] = 0x7f
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("bad JMP offset 1", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[jmpOff+1] = 0xff // into "something"
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("bad JMP offset 2", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[jmpOff+1] = byte(pushOff - jmpOff + 1)
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("out of bounds JMPL 1", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[jmplOff+1] = byte(-jmplOff - 1)
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("out of bounds JMPL 1", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[jmplOff+1] = byte(len(bad)-jmplOff) + 1
|
|
bad[jmplOff+2] = 0
|
|
bad[jmplOff+3] = 0
|
|
bad[jmplOff+4] = 0
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("JMP to a len(script)", func(t *testing.T) {
|
|
bad := make([]byte, 64) // 64 is the word-size of a bitset.
|
|
bad[0] = byte(opcode.JMP)
|
|
bad[1] = 64
|
|
require.NoError(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("bad JMPL offset", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[jmplOff+1] = 0xfe // into JMP
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("out of bounds TRY 1", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[tryOff+1] = byte(-tryOff - 1)
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("out of bounds TRY 2", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[tryOff+1] = byte(len(bad)-tryOff) + 1
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("out of bounds TRY 2", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[tryOff+2] = byte(len(bad)-tryOff) + 1
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("TRY with jumps to a len(script)", func(t *testing.T) {
|
|
bad := make([]byte, 64) // 64 is the word-size of a bitset.
|
|
bad[0] = byte(opcode.TRY)
|
|
bad[1] = 64
|
|
bad[2] = 64
|
|
bad[3] = byte(opcode.RET) // pad so that remaining script (PUSHINT8 0) is even in length.
|
|
require.NoError(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("bad TRYL offset 1", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[trylOff+1] = byte(-(trylOff - jmpOff) - 1) // into "something"
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("bad TRYL offset 2", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[trylOff+5] = byte(len(bad) - trylOff - 1)
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("bad ISTYPE type", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[istypeOff+1] = byte(0xff)
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("bad ISTYPE type (Any)", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[istypeOff+1] = byte(stackitem.AnyT)
|
|
require.Error(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("good NEWARRAY_T type", func(t *testing.T) {
|
|
bad := slices.Clone(good)
|
|
bad[istypeOff] = byte(opcode.NEWARRAYT)
|
|
bad[istypeOff+1] = byte(stackitem.AnyT)
|
|
require.NoError(t, IsScriptCorrect(bad, nil))
|
|
})
|
|
|
|
t.Run("good methods", func(t *testing.T) {
|
|
methods := bitfield.New(len(good))
|
|
methods.Set(retOff)
|
|
methods.Set(tryOff)
|
|
methods.Set(pushOff)
|
|
require.NoError(t, IsScriptCorrect(good, methods))
|
|
})
|
|
|
|
t.Run("bad methods", func(t *testing.T) {
|
|
methods := bitfield.New(len(good))
|
|
methods.Set(retOff)
|
|
methods.Set(tryOff)
|
|
methods.Set(pushOff + 1)
|
|
require.Error(t, IsScriptCorrect(good, methods))
|
|
})
|
|
}
|