neo-go/pkg/vm/contract_checks_test.go
Roman Khimov 1b83dc2476 *: improve for loop syntax
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>
2024-08-30 21:45:18 +03:00

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))
})
}