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 }