github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/vm/contract_checks_test.go (about)

     1  package vm
     2  
     3  import (
     4  	"encoding/binary"
     5  	"testing"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
     8  	"github.com/nspcc-dev/neo-go/pkg/io"
     9  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    10  	"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
    11  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    13  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func testSignatureContract() []byte {
    19  	prog := make([]byte, 40)
    20  	prog[0] = byte(opcode.PUSHDATA1)
    21  	prog[1] = 33
    22  	prog[35] = byte(opcode.SYSCALL)
    23  	binary.LittleEndian.PutUint32(prog[36:], verifyInteropID)
    24  	return prog
    25  }
    26  
    27  func TestParseSignatureContract(t *testing.T) {
    28  	prog := testSignatureContract()
    29  	pub := randomBytes(33)
    30  	copy(prog[2:], pub)
    31  	actual, ok := ParseSignatureContract(prog)
    32  	require.True(t, ok)
    33  	require.Equal(t, pub, actual)
    34  }
    35  
    36  func TestIsSignatureContract(t *testing.T) {
    37  	t.Run("valid contract", func(t *testing.T) {
    38  		prog := testSignatureContract()
    39  		assert.True(t, IsSignatureContract(prog))
    40  		assert.True(t, IsStandardContract(prog))
    41  	})
    42  
    43  	t.Run("invalid interop ID", func(t *testing.T) {
    44  		prog := testSignatureContract()
    45  		binary.LittleEndian.PutUint32(prog[36:], ^verifyInteropID)
    46  		assert.False(t, IsSignatureContract(prog))
    47  		assert.False(t, IsStandardContract(prog))
    48  	})
    49  
    50  	t.Run("invalid pubkey size", func(t *testing.T) {
    51  		prog := testSignatureContract()
    52  		prog[1] = 32
    53  		assert.False(t, IsSignatureContract(prog))
    54  		assert.False(t, IsStandardContract(prog))
    55  	})
    56  
    57  	t.Run("invalid length", func(t *testing.T) {
    58  		prog := testSignatureContract()
    59  		prog = append(prog, 0)
    60  		assert.False(t, IsSignatureContract(prog))
    61  		assert.False(t, IsStandardContract(prog))
    62  	})
    63  }
    64  
    65  func testMultisigContract(t *testing.T, n, m int) []byte {
    66  	pubs := make(keys.PublicKeys, n)
    67  	for i := 0; i < n; i++ {
    68  		priv, err := keys.NewPrivateKey()
    69  		require.NoError(t, err)
    70  		pubs[i] = priv.PublicKey()
    71  	}
    72  
    73  	prog, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
    74  	require.NoError(t, err)
    75  	return prog
    76  }
    77  
    78  func TestIsMultiSigContract(t *testing.T) {
    79  	t.Run("valid contract", func(t *testing.T) {
    80  		prog := testMultisigContract(t, 2, 2)
    81  		assert.True(t, IsMultiSigContract(prog))
    82  		assert.True(t, IsStandardContract(prog))
    83  	})
    84  
    85  	t.Run("0-length", func(t *testing.T) {
    86  		assert.False(t, IsMultiSigContract([]byte{}))
    87  	})
    88  
    89  	t.Run("invalid param", func(t *testing.T) {
    90  		prog := []byte{byte(opcode.PUSHDATA1), 10}
    91  		assert.False(t, IsMultiSigContract(prog))
    92  	})
    93  
    94  	t.Run("too many keys", func(t *testing.T) {
    95  		prog := testMultisigContract(t, 1025, 1)
    96  		assert.False(t, IsMultiSigContract(prog))
    97  	})
    98  
    99  	t.Run("invalid interop ID", func(t *testing.T) {
   100  		prog := testMultisigContract(t, 2, 2)
   101  		prog[len(prog)-4] ^= 0xFF
   102  		assert.False(t, IsMultiSigContract(prog))
   103  	})
   104  
   105  	t.Run("invalid keys number", func(t *testing.T) {
   106  		prog := testMultisigContract(t, 2, 2)
   107  		prog[len(prog)-6] = byte(opcode.PUSH3)
   108  		assert.False(t, IsMultiSigContract(prog))
   109  	})
   110  
   111  	t.Run("invalid length", func(t *testing.T) {
   112  		prog := testMultisigContract(t, 2, 2)
   113  		prog = append(prog, 0)
   114  		assert.False(t, IsMultiSigContract(prog))
   115  	})
   116  }
   117  
   118  func TestIsScriptCorrect(t *testing.T) {
   119  	w := io.NewBufBinWriter()
   120  	emit.String(w.BinWriter, "something")
   121  
   122  	jmpOff := w.Len()
   123  	emit.Opcodes(w.BinWriter, opcode.JMP, opcode.Opcode(-jmpOff))
   124  
   125  	retOff := w.Len()
   126  	emit.Opcodes(w.BinWriter, opcode.RET)
   127  
   128  	jmplOff := w.Len()
   129  	emit.Opcodes(w.BinWriter, opcode.JMPL, opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff))
   130  
   131  	tryOff := w.Len()
   132  	emit.Opcodes(w.BinWriter, opcode.TRY, opcode.Opcode(3), opcode.Opcode(0xfb)) // -5
   133  
   134  	trylOff := w.Len()
   135  	emit.Opcodes(w.BinWriter, opcode.TRYL, opcode.Opcode(0xfd), opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff),
   136  		opcode.Opcode(9), opcode.Opcode(0), opcode.Opcode(0), opcode.Opcode(0))
   137  
   138  	istypeOff := w.Len()
   139  	emit.Opcodes(w.BinWriter, opcode.ISTYPE, opcode.Opcode(stackitem.IntegerT))
   140  
   141  	pushOff := w.Len()
   142  	emit.String(w.BinWriter, "else")
   143  
   144  	good := w.Bytes()
   145  
   146  	getScript := func() []byte {
   147  		s := make([]byte, len(good))
   148  		copy(s, good)
   149  		return s
   150  	}
   151  
   152  	t.Run("good", func(t *testing.T) {
   153  		require.NoError(t, IsScriptCorrect(good, nil))
   154  	})
   155  
   156  	t.Run("bad instruction", func(t *testing.T) {
   157  		bad := getScript()
   158  		bad[retOff] = 0xff
   159  		require.Error(t, IsScriptCorrect(bad, nil))
   160  	})
   161  
   162  	t.Run("out of bounds JMP 1", func(t *testing.T) {
   163  		bad := getScript()
   164  		bad[jmpOff+1] = 0x80 // -128
   165  		require.Error(t, IsScriptCorrect(bad, nil))
   166  	})
   167  
   168  	t.Run("out of bounds JMP 2", func(t *testing.T) {
   169  		bad := getScript()
   170  		bad[jmpOff+1] = 0x7f
   171  		require.Error(t, IsScriptCorrect(bad, nil))
   172  	})
   173  
   174  	t.Run("bad JMP offset 1", func(t *testing.T) {
   175  		bad := getScript()
   176  		bad[jmpOff+1] = 0xff // into "something"
   177  		require.Error(t, IsScriptCorrect(bad, nil))
   178  	})
   179  
   180  	t.Run("bad JMP offset 2", func(t *testing.T) {
   181  		bad := getScript()
   182  		bad[jmpOff+1] = byte(pushOff - jmpOff + 1)
   183  		require.Error(t, IsScriptCorrect(bad, nil))
   184  	})
   185  
   186  	t.Run("out of bounds JMPL 1", func(t *testing.T) {
   187  		bad := getScript()
   188  		bad[jmplOff+1] = byte(-jmplOff - 1)
   189  		require.Error(t, IsScriptCorrect(bad, nil))
   190  	})
   191  
   192  	t.Run("out of bounds JMPL 1", func(t *testing.T) {
   193  		bad := getScript()
   194  		bad[jmplOff+1] = byte(len(bad)-jmplOff) + 1
   195  		bad[jmplOff+2] = 0
   196  		bad[jmplOff+3] = 0
   197  		bad[jmplOff+4] = 0
   198  		require.Error(t, IsScriptCorrect(bad, nil))
   199  	})
   200  
   201  	t.Run("JMP to a len(script)", func(t *testing.T) {
   202  		bad := make([]byte, 64) // 64 is the word-size of a bitset.
   203  		bad[0] = byte(opcode.JMP)
   204  		bad[1] = 64
   205  		require.NoError(t, IsScriptCorrect(bad, nil))
   206  	})
   207  
   208  	t.Run("bad JMPL offset", func(t *testing.T) {
   209  		bad := getScript()
   210  		bad[jmplOff+1] = 0xfe // into JMP
   211  		require.Error(t, IsScriptCorrect(bad, nil))
   212  	})
   213  
   214  	t.Run("out of bounds TRY 1", func(t *testing.T) {
   215  		bad := getScript()
   216  		bad[tryOff+1] = byte(-tryOff - 1)
   217  		require.Error(t, IsScriptCorrect(bad, nil))
   218  	})
   219  
   220  	t.Run("out of bounds TRY 2", func(t *testing.T) {
   221  		bad := getScript()
   222  		bad[tryOff+1] = byte(len(bad)-tryOff) + 1
   223  		require.Error(t, IsScriptCorrect(bad, nil))
   224  	})
   225  
   226  	t.Run("out of bounds TRY 2", func(t *testing.T) {
   227  		bad := getScript()
   228  		bad[tryOff+2] = byte(len(bad)-tryOff) + 1
   229  		require.Error(t, IsScriptCorrect(bad, nil))
   230  	})
   231  
   232  	t.Run("TRY with jumps to a len(script)", func(t *testing.T) {
   233  		bad := make([]byte, 64) // 64 is the word-size of a bitset.
   234  		bad[0] = byte(opcode.TRY)
   235  		bad[1] = 64
   236  		bad[2] = 64
   237  		bad[3] = byte(opcode.RET) // pad so that remaining script (PUSHINT8 0) is even in length.
   238  		require.NoError(t, IsScriptCorrect(bad, nil))
   239  	})
   240  
   241  	t.Run("bad TRYL offset 1", func(t *testing.T) {
   242  		bad := getScript()
   243  		bad[trylOff+1] = byte(-(trylOff - jmpOff) - 1) // into "something"
   244  		require.Error(t, IsScriptCorrect(bad, nil))
   245  	})
   246  
   247  	t.Run("bad TRYL offset 2", func(t *testing.T) {
   248  		bad := getScript()
   249  		bad[trylOff+5] = byte(len(bad) - trylOff - 1)
   250  		require.Error(t, IsScriptCorrect(bad, nil))
   251  	})
   252  
   253  	t.Run("bad ISTYPE type", func(t *testing.T) {
   254  		bad := getScript()
   255  		bad[istypeOff+1] = byte(0xff)
   256  		require.Error(t, IsScriptCorrect(bad, nil))
   257  	})
   258  
   259  	t.Run("bad ISTYPE type (Any)", func(t *testing.T) {
   260  		bad := getScript()
   261  		bad[istypeOff+1] = byte(stackitem.AnyT)
   262  		require.Error(t, IsScriptCorrect(bad, nil))
   263  	})
   264  
   265  	t.Run("good NEWARRAY_T type", func(t *testing.T) {
   266  		bad := getScript()
   267  		bad[istypeOff] = byte(opcode.NEWARRAYT)
   268  		bad[istypeOff+1] = byte(stackitem.AnyT)
   269  		require.NoError(t, IsScriptCorrect(bad, nil))
   270  	})
   271  
   272  	t.Run("good methods", func(t *testing.T) {
   273  		methods := bitfield.New(len(good))
   274  		methods.Set(retOff)
   275  		methods.Set(tryOff)
   276  		methods.Set(pushOff)
   277  		require.NoError(t, IsScriptCorrect(good, methods))
   278  	})
   279  
   280  	t.Run("bad methods", func(t *testing.T) {
   281  		methods := bitfield.New(len(good))
   282  		methods.Set(retOff)
   283  		methods.Set(tryOff)
   284  		methods.Set(pushOff + 1)
   285  		require.Error(t, IsScriptCorrect(good, methods))
   286  	})
   287  }