github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/internal/contracts/contracts_test.go (about) 1 package contracts 2 3 import ( 4 "encoding/json" 5 "os" 6 "testing" 7 8 "github.com/nspcc-dev/neo-go/pkg/config" 9 "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" 10 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 11 "github.com/nspcc-dev/neo-go/pkg/core/state" 12 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 13 "github.com/nspcc-dev/neo-go/pkg/io" 14 "github.com/nspcc-dev/neo-go/pkg/neotest" 15 "github.com/nspcc-dev/neo-go/pkg/neotest/chain" 16 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 17 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 18 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 19 "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" 20 "github.com/nspcc-dev/neo-go/pkg/util" 21 "github.com/nspcc-dev/neo-go/pkg/vm/emit" 22 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 23 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 24 "github.com/stretchr/testify/require" 25 ) 26 27 // TestGenerateHelperContracts generates contract states that are used in tests. 28 // See generateOracleContract and generateManagementHelperContracts comments for 29 // details. 30 func TestGenerateHelperContracts(t *testing.T) { 31 const saveState = false 32 33 generateOracleContract(t, saveState) 34 generateManagementHelperContracts(t, saveState) 35 36 require.False(t, saveState) 37 } 38 39 // generateOracleContract generates a helper contract that is able to call 40 // the native Oracle contract and has callback method. It uses testchain to define 41 // Oracle and StdLib native hashes and saves the generated NEF and manifest to `oracle_contract` folder. 42 // Set `saveState` flag to true and run the test to rewrite NEF and manifest files. 43 func generateOracleContract(t *testing.T, saveState bool) { 44 ctr := neotest.CompileFile(t, util.Uint160{}, oracleContractModPath, oracleContractYAMLPath) 45 46 // Write NEF file. 47 bytes, err := ctr.NEF.Bytes() 48 require.NoError(t, err) 49 if saveState { 50 err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm) 51 require.NoError(t, err) 52 } 53 54 // Write manifest file. 55 mData, err := json.Marshal(ctr.Manifest) 56 require.NoError(t, err) 57 if saveState { 58 err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm) 59 require.NoError(t, err) 60 } 61 } 62 63 // generateManagementHelperContracts generates 2 helper contracts, second of which is 64 // allowed to call the first. It uses testchain to define Management and StdLib 65 // native hashes and saves the generated NEF and manifest to `management_contract` folder. 66 // Set `saveState` flag to true and run the test to rewrite NEF and manifest files. 67 func generateManagementHelperContracts(t *testing.T, saveState bool) { 68 bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) { 69 c.P2PSigExtensions = true 70 }) 71 e := neotest.NewExecutor(t, bc, validator, committee) 72 73 mgmtHash := e.NativeHash(t, nativenames.Management) 74 stdHash := e.NativeHash(t, nativenames.StdLib) 75 neoHash := e.NativeHash(t, nativenames.Neo) 76 singleChainValidatorAcc := e.Validator.(neotest.MultiSigner).Single(2).Account() // priv0 77 require.NoError(t, singleChainValidatorAcc.ConvertMultisig(1, keys.PublicKeys{singleChainValidatorAcc.PublicKey()})) 78 singleChainValidatorHash := singleChainValidatorAcc.Contract.ScriptHash() 79 80 w := io.NewBufBinWriter() 81 emit.Opcodes(w.BinWriter, opcode.ABORT) 82 addOff := w.Len() 83 emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET) 84 addMultiOff := w.Len() 85 emit.Opcodes(w.BinWriter, opcode.ADD, opcode.ADD, opcode.RET) 86 ret7Off := w.Len() 87 emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET) 88 dropOff := w.Len() 89 emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET) 90 initOff := w.Len() 91 emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET) 92 add3Off := w.Len() 93 emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET) 94 invalidRetOff := w.Len() 95 emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET) 96 justRetOff := w.Len() 97 emit.Opcodes(w.BinWriter, opcode.RET) 98 verifyOff := w.Len() 99 emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB, 100 opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET) 101 deployOff := w.Len() 102 emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3) 103 emit.String(w.BinWriter, "create") // 8 bytes 104 emit.Int(w.BinWriter, 2) // 1 byte 105 emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte 106 emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) 107 emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) 108 emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes 109 emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+3, opcode.RET) 110 emit.String(w.BinWriter, "update") // 8 bytes 111 emit.Int(w.BinWriter, 2) // 1 byte 112 emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte 113 emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) 114 emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) 115 emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes 116 emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET) 117 putValOff := w.Len() 118 emit.String(w.BinWriter, "initial") 119 emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) 120 emit.Syscall(w.BinWriter, interopnames.SystemStoragePut) 121 emit.Opcodes(w.BinWriter, opcode.RET) 122 getValOff := w.Len() 123 emit.String(w.BinWriter, "initial") 124 emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) 125 emit.Syscall(w.BinWriter, interopnames.SystemStorageGet) 126 emit.Opcodes(w.BinWriter, opcode.RET) 127 delValOff := w.Len() 128 emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) 129 emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete) 130 emit.Opcodes(w.BinWriter, opcode.RET) 131 onNEP17PaymentOff := w.Len() 132 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash) 133 emit.Int(w.BinWriter, 4) 134 emit.Opcodes(w.BinWriter, opcode.PACK) 135 emit.String(w.BinWriter, "LastPaymentNEP17") 136 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) 137 emit.Opcodes(w.BinWriter, opcode.RET) 138 onNEP11PaymentOff := w.Len() 139 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash) 140 emit.Int(w.BinWriter, 5) 141 emit.Opcodes(w.BinWriter, opcode.PACK) 142 emit.String(w.BinWriter, "LastPaymentNEP11") 143 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) 144 emit.Opcodes(w.BinWriter, opcode.RET) 145 update3Off := w.Len() 146 emit.Int(w.BinWriter, 3) 147 emit.Opcodes(w.BinWriter, opcode.JMP, 2+1) 148 updateOff := w.Len() 149 emit.Int(w.BinWriter, 2) 150 emit.Opcodes(w.BinWriter, opcode.PACK) 151 emit.AppCallNoArgs(w.BinWriter, mgmtHash, "update", callflag.All) 152 emit.Opcodes(w.BinWriter, opcode.DROP) 153 emit.Opcodes(w.BinWriter, opcode.RET) 154 destroyOff := w.Len() 155 emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All) 156 emit.Opcodes(w.BinWriter, opcode.DROP) 157 emit.Opcodes(w.BinWriter, opcode.RET) 158 invalidStack1Off := w.Len() 159 emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array 160 emit.Opcodes(w.BinWriter, opcode.RET) 161 invalidStack2Off := w.Len() 162 emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item 163 emit.Opcodes(w.BinWriter, opcode.RET) 164 callT0Off := w.Len() 165 emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET) 166 callT1Off := w.Len() 167 emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET) 168 callT2Off := w.Len() 169 emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET) 170 burnGasOff := w.Len() 171 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas) 172 emit.Opcodes(w.BinWriter, opcode.RET) 173 invocCounterOff := w.Len() 174 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetInvocationCounter) 175 emit.Opcodes(w.BinWriter, opcode.RET) 176 177 script := w.Bytes() 178 m := manifest.NewManifest("TestMain") 179 m.ABI.Methods = []manifest.Method{ 180 { 181 Name: "add", 182 Offset: addOff, 183 Parameters: []manifest.Parameter{ 184 manifest.NewParameter("addend1", smartcontract.IntegerType), 185 manifest.NewParameter("addend2", smartcontract.IntegerType), 186 }, 187 ReturnType: smartcontract.IntegerType, 188 }, 189 { 190 Name: "add", 191 Offset: addMultiOff, 192 Parameters: []manifest.Parameter{ 193 manifest.NewParameter("addend1", smartcontract.IntegerType), 194 manifest.NewParameter("addend2", smartcontract.IntegerType), 195 manifest.NewParameter("addend3", smartcontract.IntegerType), 196 }, 197 ReturnType: smartcontract.IntegerType, 198 }, 199 { 200 Name: "ret7", 201 Offset: ret7Off, 202 Parameters: []manifest.Parameter{}, 203 ReturnType: smartcontract.IntegerType, 204 }, 205 { 206 Name: "drop", 207 Offset: dropOff, 208 ReturnType: smartcontract.VoidType, 209 }, 210 { 211 Name: manifest.MethodInit, 212 Offset: initOff, 213 ReturnType: smartcontract.VoidType, 214 }, 215 { 216 Name: "add3", 217 Offset: add3Off, 218 Parameters: []manifest.Parameter{ 219 manifest.NewParameter("addend", smartcontract.IntegerType), 220 }, 221 ReturnType: smartcontract.IntegerType, 222 }, 223 { 224 Name: "invalidReturn", 225 Offset: invalidRetOff, 226 ReturnType: smartcontract.IntegerType, 227 }, 228 { 229 Name: "justReturn", 230 Offset: justRetOff, 231 ReturnType: smartcontract.VoidType, 232 }, 233 { 234 Name: manifest.MethodVerify, 235 Offset: verifyOff, 236 ReturnType: smartcontract.BoolType, 237 }, 238 { 239 Name: manifest.MethodDeploy, 240 Offset: deployOff, 241 Parameters: []manifest.Parameter{ 242 manifest.NewParameter("data", smartcontract.AnyType), 243 manifest.NewParameter("isUpdate", smartcontract.BoolType), 244 }, 245 ReturnType: smartcontract.VoidType, 246 }, 247 { 248 Name: "getValue", 249 Offset: getValOff, 250 ReturnType: smartcontract.StringType, 251 }, 252 { 253 Name: "putValue", 254 Offset: putValOff, 255 Parameters: []manifest.Parameter{ 256 manifest.NewParameter("value", smartcontract.StringType), 257 }, 258 ReturnType: smartcontract.VoidType, 259 }, 260 { 261 Name: "delValue", 262 Offset: delValOff, 263 Parameters: []manifest.Parameter{ 264 manifest.NewParameter("key", smartcontract.StringType), 265 }, 266 ReturnType: smartcontract.VoidType, 267 }, 268 { 269 Name: manifest.MethodOnNEP11Payment, 270 Offset: onNEP11PaymentOff, 271 Parameters: []manifest.Parameter{ 272 manifest.NewParameter("from", smartcontract.Hash160Type), 273 manifest.NewParameter("amount", smartcontract.IntegerType), 274 manifest.NewParameter("tokenid", smartcontract.ByteArrayType), 275 manifest.NewParameter("data", smartcontract.AnyType), 276 }, 277 ReturnType: smartcontract.VoidType, 278 }, 279 { 280 Name: manifest.MethodOnNEP17Payment, 281 Offset: onNEP17PaymentOff, 282 Parameters: []manifest.Parameter{ 283 manifest.NewParameter("from", smartcontract.Hash160Type), 284 manifest.NewParameter("amount", smartcontract.IntegerType), 285 manifest.NewParameter("data", smartcontract.AnyType), 286 }, 287 ReturnType: smartcontract.VoidType, 288 }, 289 { 290 Name: "update", 291 Offset: updateOff, 292 Parameters: []manifest.Parameter{ 293 manifest.NewParameter("nef", smartcontract.ByteArrayType), 294 manifest.NewParameter("manifest", smartcontract.ByteArrayType), 295 }, 296 ReturnType: smartcontract.VoidType, 297 }, 298 { 299 Name: "update", 300 Offset: update3Off, 301 Parameters: []manifest.Parameter{ 302 manifest.NewParameter("nef", smartcontract.ByteArrayType), 303 manifest.NewParameter("manifest", smartcontract.ByteArrayType), 304 manifest.NewParameter("data", smartcontract.AnyType), 305 }, 306 ReturnType: smartcontract.VoidType, 307 }, 308 { 309 Name: "destroy", 310 Offset: destroyOff, 311 ReturnType: smartcontract.VoidType, 312 }, 313 { 314 Name: "invalidStack1", 315 Offset: invalidStack1Off, 316 ReturnType: smartcontract.AnyType, 317 }, 318 { 319 Name: "invalidStack2", 320 Offset: invalidStack2Off, 321 ReturnType: smartcontract.AnyType, 322 }, 323 { 324 Name: "callT0", 325 Offset: callT0Off, 326 Parameters: []manifest.Parameter{ 327 manifest.NewParameter("address", smartcontract.Hash160Type), 328 }, 329 ReturnType: smartcontract.IntegerType, 330 }, 331 { 332 Name: "callT1", 333 Offset: callT1Off, 334 ReturnType: smartcontract.IntegerType, 335 }, 336 { 337 Name: "callT2", 338 Offset: callT2Off, 339 ReturnType: smartcontract.IntegerType, 340 }, 341 { 342 Name: "burnGas", 343 Offset: burnGasOff, 344 Parameters: []manifest.Parameter{ 345 manifest.NewParameter("amount", smartcontract.IntegerType), 346 }, 347 ReturnType: smartcontract.VoidType, 348 }, 349 { 350 Name: "invocCounter", 351 Offset: invocCounterOff, 352 ReturnType: smartcontract.IntegerType, 353 }, 354 } 355 m.ABI.Events = []manifest.Event{ 356 { 357 Name: "LastPaymentNEP17", 358 Parameters: []manifest.Parameter{ 359 { 360 Name: "from", 361 Type: smartcontract.Hash160Type, 362 }, 363 { 364 Name: "to", 365 Type: smartcontract.Hash160Type, 366 }, 367 { 368 Name: "amount", 369 Type: smartcontract.IntegerType, 370 }, 371 { 372 Name: "data", 373 Type: smartcontract.AnyType, 374 }, 375 }, 376 }, 377 { 378 Name: "LastPaymentNEP11", 379 Parameters: []manifest.Parameter{ 380 { 381 Name: "from", 382 Type: smartcontract.Hash160Type, 383 }, 384 { 385 Name: "to", 386 Type: smartcontract.Hash160Type, 387 }, 388 { 389 Name: "amount", 390 Type: smartcontract.IntegerType, 391 }, 392 { 393 Name: "tokenId", 394 Type: smartcontract.ByteArrayType, 395 }, 396 { 397 Name: "data", 398 Type: smartcontract.AnyType, 399 }, 400 }, 401 }, 402 { 403 Name: "event", // This event is not emitted by the contract code and needed for System.Runtime.Notify tests. 404 Parameters: []manifest.Parameter{ 405 { 406 Name: "any", 407 Type: smartcontract.AnyType, 408 }, 409 }, 410 }, 411 } 412 m.Permissions = make([]manifest.Permission, 2) 413 m.Permissions[0].Contract.Type = manifest.PermissionHash 414 m.Permissions[0].Contract.Value = neoHash 415 m.Permissions[0].Methods.Add("balanceOf") 416 417 m.Permissions[1].Contract.Type = manifest.PermissionHash 418 m.Permissions[1].Contract.Value = util.Uint160{} 419 m.Permissions[1].Methods.Add("method") 420 421 // Generate NEF file. 422 ne, err := nef.NewFile(script) 423 require.NoError(t, err) 424 ne.Tokens = []nef.MethodToken{ 425 { 426 Hash: neoHash, 427 Method: "balanceOf", 428 ParamCount: 1, 429 HasReturn: true, 430 CallFlag: callflag.ReadStates, 431 }, 432 { 433 Hash: util.Uint160{}, 434 Method: "method", 435 HasReturn: true, 436 CallFlag: callflag.ReadStates, 437 }, 438 } 439 ne.Checksum = ne.CalculateChecksum() 440 441 // Write first NEF file. 442 bytes, err := ne.Bytes() 443 require.NoError(t, err) 444 if saveState { 445 err = os.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm) 446 require.NoError(t, err) 447 } 448 449 // Write first manifest file. 450 mData, err := json.Marshal(m) 451 require.NoError(t, err) 452 if saveState { 453 err = os.WriteFile(helper1ContractManifestPath, mData, os.ModePerm) 454 require.NoError(t, err) 455 } 456 457 // Create hash of the first contract assuming that sender is single-chain validator. 458 h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name) 459 460 currScript := []byte{byte(opcode.RET)} 461 m = manifest.NewManifest("TestAux") 462 m.ABI.Methods = []manifest.Method{ 463 { 464 Name: "simpleMethod", 465 Offset: 0, 466 ReturnType: smartcontract.VoidType, 467 }, 468 } 469 perm := manifest.NewPermission(manifest.PermissionHash, h) 470 perm.Methods.Add("add") 471 perm.Methods.Add("drop") 472 perm.Methods.Add("add3") 473 perm.Methods.Add("invalidReturn") 474 perm.Methods.Add("justReturn") 475 perm.Methods.Add("getValue") 476 m.Permissions = append(m.Permissions, *perm) 477 ne, err = nef.NewFile(currScript) 478 require.NoError(t, err) 479 480 // Write second NEF file. 481 bytes, err = ne.Bytes() 482 require.NoError(t, err) 483 if saveState { 484 err = os.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm) 485 require.NoError(t, err) 486 } 487 488 // Write second manifest file. 489 mData, err = json.Marshal(m) 490 require.NoError(t, err) 491 if saveState { 492 err = os.WriteFile(helper2ContractManifestPath, mData, os.ModePerm) 493 require.NoError(t, err) 494 } 495 }