github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/interop/runtime/ext_test.go (about) 1 package runtime_test 2 3 import ( 4 "encoding/json" 5 "math" 6 "math/big" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/nspcc-dev/neo-go/internal/contracts" 12 "github.com/nspcc-dev/neo-go/internal/random" 13 "github.com/nspcc-dev/neo-go/pkg/config" 14 "github.com/nspcc-dev/neo-go/pkg/config/netmode" 15 "github.com/nspcc-dev/neo-go/pkg/core" 16 "github.com/nspcc-dev/neo-go/pkg/core/block" 17 "github.com/nspcc-dev/neo-go/pkg/core/interop" 18 "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" 19 "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" 20 "github.com/nspcc-dev/neo-go/pkg/core/native" 21 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 22 "github.com/nspcc-dev/neo-go/pkg/core/state" 23 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 24 "github.com/nspcc-dev/neo-go/pkg/crypto/hash" 25 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 26 "github.com/nspcc-dev/neo-go/pkg/encoding/address" 27 "github.com/nspcc-dev/neo-go/pkg/io" 28 "github.com/nspcc-dev/neo-go/pkg/neotest" 29 "github.com/nspcc-dev/neo-go/pkg/neotest/chain" 30 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 31 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 32 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 33 "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" 34 "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" 35 "github.com/nspcc-dev/neo-go/pkg/util" 36 "github.com/nspcc-dev/neo-go/pkg/vm" 37 "github.com/nspcc-dev/neo-go/pkg/vm/emit" 38 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 39 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 40 "github.com/stretchr/testify/require" 41 ) 42 43 var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", "contracts") 44 45 func getSharpTestTx(sender util.Uint160) *transaction.Transaction { 46 tx := transaction.New([]byte{byte(opcode.PUSH2)}, 0) 47 tx.Nonce = 0 48 tx.Signers = append(tx.Signers, transaction.Signer{ 49 Account: sender, 50 Scopes: transaction.CalledByEntry, 51 }) 52 tx.Attributes = []transaction.Attribute{} 53 tx.Scripts = append(tx.Scripts, transaction.Witness{InvocationScript: []byte{}, VerificationScript: []byte{}}) 54 return tx 55 } 56 57 func getSharpTestGenesis(t *testing.T) *block.Block { 58 const configPath = "../../../../config" 59 60 cfg, err := config.Load(configPath, netmode.MainNet) 61 require.NoError(t, err) 62 b, err := core.CreateGenesisBlock(cfg.ProtocolConfiguration) 63 require.NoError(t, err) 64 return b 65 } 66 67 func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) { 68 chain, _ := chain.NewSingle(t) 69 ic, err := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) 70 require.NoError(t, err) 71 v := ic.SpawnVM() 72 return v, ic, chain 73 } 74 75 func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Uint160, f callflag.CallFlag, args ...any) { 76 ic.SpawnVM() 77 ic.VM.LoadScriptWithHash(script, hash, f) 78 for i := range args { 79 ic.VM.Estack().PushVal(args[i]) 80 } 81 ic.VM.GasLimit = -1 82 } 83 84 func wrapDynamicScript(t *testing.T, script []byte, flags callflag.CallFlag, args ...any) []byte { 85 b := io.NewBufBinWriter() 86 87 // Params. 88 emit.Array(b.BinWriter, args...) 89 emit.Int(b.BinWriter, int64(flags)) 90 emit.Bytes(b.BinWriter, script) 91 92 // Wrapped syscall. 93 emit.Instruction(b.BinWriter, opcode.TRY, []byte{3 + 5 + 2, 0}) // 3 94 emit.Syscall(b.BinWriter, interopnames.SystemRuntimeLoadScript) // 5 95 emit.Instruction(b.BinWriter, opcode.ENDTRY, []byte{1 + 11 + 2}) // 2 96 97 // Catch block 98 emit.Opcodes(b.BinWriter, opcode.DROP) 99 emit.String(b.BinWriter, "exception") // 1 + 1 + 9 == 11 bytes 100 emit.Opcodes(b.BinWriter, opcode.RET) 101 102 require.NoError(t, b.Err) 103 return b.Bytes() 104 } 105 106 func getDeployedInternal(t *testing.T) (*neotest.Executor, neotest.Signer, *core.Blockchain, *state.Contract) { 107 bc, acc := chain.NewSingle(t) 108 e := neotest.NewExecutor(t, bc, acc, acc) 109 managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) 110 111 cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash()) 112 rawManifest, err := json.Marshal(cs.Manifest) 113 require.NoError(t, err) 114 rawNef, err := cs.NEF.Bytes() 115 require.NoError(t, err) 116 tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest) 117 e.AddNewBlock(t, tx) 118 e.CheckHalt(t, tx.Hash()) 119 120 return e, acc, bc, cs 121 } 122 123 func TestBurnGas(t *testing.T) { 124 e, acc, _, cs := getDeployedInternal(t) 125 cInvoker := e.ValidatorInvoker(cs.Hash) 126 127 t.Run("good", func(t *testing.T) { 128 h := cInvoker.Invoke(t, stackitem.Null{}, "burnGas", int64(1)) 129 res := e.GetTxExecResult(t, h) 130 131 t.Run("gas limit exceeded", func(t *testing.T) { 132 tx := e.NewUnsignedTx(t, cs.Hash, "burnGas", int64(2)) 133 e.SignTx(t, tx, res.GasConsumed, acc) 134 e.AddNewBlock(t, tx) 135 e.CheckFault(t, tx.Hash(), "GAS limit exceeded") 136 }) 137 }) 138 t.Run("too big integer", func(t *testing.T) { 139 gas := big.NewInt(math.MaxInt64) 140 gas.Add(gas, big.NewInt(1)) 141 142 cInvoker.InvokeFail(t, "invalid GAS value", "burnGas", gas) 143 }) 144 t.Run("zero GAS", func(t *testing.T) { 145 cInvoker.InvokeFail(t, "GAS must be positive", "burnGas", int64(0)) 146 }) 147 } 148 149 func TestCheckWitness(t *testing.T) { 150 _, ic, _ := createVM(t) 151 152 script := []byte{byte(opcode.RET)} 153 scriptHash := hash.Hash160(script) 154 check := func(t *testing.T, ic *interop.Context, arg any, shouldFail bool, expected ...bool) { 155 ic.VM.Estack().PushVal(arg) 156 err := runtime.CheckWitness(ic) 157 if shouldFail { 158 require.Error(t, err) 159 } else { 160 require.NoError(t, err) 161 require.NotNil(t, expected) 162 actual, ok := ic.VM.Estack().Pop().Value().(bool) 163 require.True(t, ok) 164 require.Equal(t, expected[0], actual) 165 } 166 } 167 t.Run("error", func(t *testing.T) { 168 t.Run("not a hash or key", func(t *testing.T) { 169 check(t, ic, []byte{1, 2, 3}, true) 170 }) 171 t.Run("script container is not a transaction", func(t *testing.T) { 172 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 173 check(t, ic, random.Uint160().BytesBE(), true) 174 }) 175 t.Run("check scope", func(t *testing.T) { 176 t.Run("CustomGroups, missing ReadStates flag", func(t *testing.T) { 177 hash := random.Uint160() 178 tx := &transaction.Transaction{ 179 Signers: []transaction.Signer{ 180 { 181 Account: hash, 182 Scopes: transaction.CustomGroups, 183 AllowedGroups: []*keys.PublicKey{}, 184 }, 185 }, 186 } 187 ic.Tx = tx 188 callingScriptHash := scriptHash 189 loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All) 190 ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.AllowCall) 191 check(t, ic, hash.BytesBE(), true) 192 }) 193 t.Run("Rules, missing ReadStates flag", func(t *testing.T) { 194 hash := random.Uint160() 195 pk, err := keys.NewPrivateKey() 196 require.NoError(t, err) 197 tx := &transaction.Transaction{ 198 Signers: []transaction.Signer{ 199 { 200 Account: hash, 201 Scopes: transaction.Rules, 202 Rules: []transaction.WitnessRule{{ 203 Action: transaction.WitnessAllow, 204 Condition: (*transaction.ConditionGroup)(pk.PublicKey()), 205 }}, 206 }, 207 }, 208 } 209 ic.Tx = tx 210 callingScriptHash := scriptHash 211 loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All) 212 ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.AllowCall) 213 check(t, ic, hash.BytesBE(), true) 214 }) 215 }) 216 }) 217 t.Run("positive", func(t *testing.T) { 218 t.Run("calling scripthash", func(t *testing.T) { 219 t.Run("hashed witness", func(t *testing.T) { 220 callingScriptHash := scriptHash 221 loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All) 222 ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.All) 223 check(t, ic, callingScriptHash.BytesBE(), false, true) 224 }) 225 t.Run("keyed witness", func(t *testing.T) { 226 pk, err := keys.NewPrivateKey() 227 require.NoError(t, err) 228 callingScriptHash := pk.PublicKey().GetScriptHash() 229 loadScriptWithHashAndFlags(ic, script, callingScriptHash, callflag.All) 230 ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), callflag.All) 231 check(t, ic, pk.PublicKey().Bytes(), false, true) 232 }) 233 }) 234 t.Run("check scope", func(t *testing.T) { 235 t.Run("Global", func(t *testing.T) { 236 hash := random.Uint160() 237 tx := &transaction.Transaction{ 238 Signers: []transaction.Signer{ 239 { 240 Account: hash, 241 Scopes: transaction.Global, 242 }, 243 }, 244 } 245 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 246 ic.Tx = tx 247 check(t, ic, hash.BytesBE(), false, true) 248 }) 249 t.Run("CalledByEntry", func(t *testing.T) { 250 hash := random.Uint160() 251 tx := &transaction.Transaction{ 252 Signers: []transaction.Signer{ 253 { 254 Account: hash, 255 Scopes: transaction.CalledByEntry, 256 }, 257 }, 258 } 259 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 260 ic.Tx = tx 261 check(t, ic, hash.BytesBE(), false, true) 262 }) 263 t.Run("CustomContracts", func(t *testing.T) { 264 hash := random.Uint160() 265 tx := &transaction.Transaction{ 266 Signers: []transaction.Signer{ 267 { 268 Account: hash, 269 Scopes: transaction.CustomContracts, 270 AllowedContracts: []util.Uint160{scriptHash}, 271 }, 272 }, 273 } 274 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 275 ic.Tx = tx 276 check(t, ic, hash.BytesBE(), false, true) 277 }) 278 t.Run("CustomGroups", func(t *testing.T) { 279 t.Run("unknown scripthash", func(t *testing.T) { 280 hash := random.Uint160() 281 tx := &transaction.Transaction{ 282 Signers: []transaction.Signer{ 283 { 284 Account: hash, 285 Scopes: transaction.CustomGroups, 286 AllowedGroups: []*keys.PublicKey{}, 287 }, 288 }, 289 } 290 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 291 ic.Tx = tx 292 check(t, ic, hash.BytesBE(), false, false) 293 }) 294 t.Run("positive", func(t *testing.T) { 295 targetHash := random.Uint160() 296 pk, err := keys.NewPrivateKey() 297 require.NoError(t, err) 298 tx := &transaction.Transaction{ 299 Signers: []transaction.Signer{ 300 { 301 Account: targetHash, 302 Scopes: transaction.CustomGroups, 303 AllowedGroups: []*keys.PublicKey{pk.PublicKey()}, 304 }, 305 }, 306 } 307 contractScript := []byte{byte(opcode.PUSH1), byte(opcode.RET)} 308 contractScriptHash := hash.Hash160(contractScript) 309 ne, err := nef.NewFile(contractScript) 310 require.NoError(t, err) 311 contractState := &state.Contract{ 312 ContractBase: state.ContractBase{ 313 ID: 15, 314 Hash: contractScriptHash, 315 NEF: *ne, 316 Manifest: manifest.Manifest{ 317 Groups: []manifest.Group{{PublicKey: pk.PublicKey(), Signature: make([]byte, keys.SignatureLen)}}, 318 }, 319 }, 320 } 321 require.NoError(t, native.PutContractState(ic.DAO, contractState)) 322 loadScriptWithHashAndFlags(ic, contractScript, contractScriptHash, callflag.All) 323 ic.Tx = tx 324 check(t, ic, targetHash.BytesBE(), false, true) 325 }) 326 }) 327 t.Run("Rules", func(t *testing.T) { 328 t.Run("no match", func(t *testing.T) { 329 hash := random.Uint160() 330 tx := &transaction.Transaction{ 331 Signers: []transaction.Signer{ 332 { 333 Account: hash, 334 Scopes: transaction.Rules, 335 Rules: []transaction.WitnessRule{{ 336 Action: transaction.WitnessAllow, 337 Condition: (*transaction.ConditionScriptHash)(&hash), 338 }}, 339 }, 340 }, 341 } 342 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 343 ic.Tx = tx 344 check(t, ic, hash.BytesBE(), false, false) 345 }) 346 t.Run("allow", func(t *testing.T) { 347 hash := random.Uint160() 348 var cond = true 349 tx := &transaction.Transaction{ 350 Signers: []transaction.Signer{ 351 { 352 Account: hash, 353 Scopes: transaction.Rules, 354 Rules: []transaction.WitnessRule{{ 355 Action: transaction.WitnessAllow, 356 Condition: (*transaction.ConditionBoolean)(&cond), 357 }}, 358 }, 359 }, 360 } 361 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 362 ic.Tx = tx 363 check(t, ic, hash.BytesBE(), false, true) 364 }) 365 t.Run("deny", func(t *testing.T) { 366 hash := random.Uint160() 367 var cond = true 368 tx := &transaction.Transaction{ 369 Signers: []transaction.Signer{ 370 { 371 Account: hash, 372 Scopes: transaction.Rules, 373 Rules: []transaction.WitnessRule{{ 374 Action: transaction.WitnessDeny, 375 Condition: (*transaction.ConditionBoolean)(&cond), 376 }}, 377 }, 378 }, 379 } 380 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 381 ic.Tx = tx 382 check(t, ic, hash.BytesBE(), false, false) 383 }) 384 }) 385 t.Run("bad scope", func(t *testing.T) { 386 hash := random.Uint160() 387 tx := &transaction.Transaction{ 388 Signers: []transaction.Signer{ 389 { 390 Account: hash, 391 Scopes: transaction.None, 392 }, 393 }, 394 } 395 loadScriptWithHashAndFlags(ic, script, scriptHash, callflag.ReadStates) 396 ic.Tx = tx 397 check(t, ic, hash.BytesBE(), false, false) 398 }) 399 }) 400 }) 401 } 402 403 func TestLoadScript(t *testing.T) { 404 bc, acc := chain.NewSingle(t) 405 e := neotest.NewExecutor(t, bc, acc, acc) 406 407 t.Run("no ret val", func(t *testing.T) { 408 script := wrapDynamicScript(t, []byte{byte(opcode.RET)}, callflag.All) 409 e.InvokeScriptCheckHALT(t, script, []neotest.Signer{acc}, stackitem.Null{}) 410 }) 411 t.Run("empty script", func(t *testing.T) { 412 script := wrapDynamicScript(t, []byte{}, callflag.All) 413 e.InvokeScriptCheckHALT(t, script, []neotest.Signer{acc}, stackitem.Null{}) 414 }) 415 t.Run("bad script", func(t *testing.T) { 416 script := wrapDynamicScript(t, []byte{0xff}, callflag.All) 417 e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "invalid script") 418 }) 419 t.Run("ret val, no params", func(t *testing.T) { 420 script := wrapDynamicScript(t, []byte{byte(opcode.PUSH1)}, callflag.All) 421 e.InvokeScriptCheckHALT(t, script, []neotest.Signer{acc}, stackitem.Make(1)) 422 }) 423 t.Run("ret val with params", func(t *testing.T) { 424 script := wrapDynamicScript(t, []byte{byte(opcode.MUL)}, callflag.All, 2, 2) 425 e.InvokeScriptCheckHALT(t, script, []neotest.Signer{acc}, stackitem.Make(4)) 426 }) 427 t.Run("two retrun values", func(t *testing.T) { 428 script := wrapDynamicScript(t, []byte{byte(opcode.PUSH1), byte(opcode.PUSH1)}, callflag.All, 2, 2) 429 e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "multiple return values in a cross-contract call") 430 }) 431 t.Run("invalid flags", func(t *testing.T) { 432 script := wrapDynamicScript(t, []byte{byte(opcode.MUL)}, callflag.CallFlag(0xff), 2, 2) 433 e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "call flags out of range") 434 }) 435 t.Run("abort", func(t *testing.T) { 436 script := wrapDynamicScript(t, []byte{byte(opcode.ABORT)}, callflag.All) 437 e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "ABORT") 438 }) 439 t.Run("internal call", func(t *testing.T) { 440 script, err := smartcontract.CreateCallScript(e.NativeHash(t, nativenames.Gas), "decimals") 441 require.NoError(t, err) 442 script = wrapDynamicScript(t, script, callflag.ReadOnly) 443 e.InvokeScriptCheckHALT(t, script, []neotest.Signer{acc}, stackitem.Make(8)) 444 }) 445 t.Run("forbidden internal call", func(t *testing.T) { 446 script, err := smartcontract.CreateCallScript(e.NativeHash(t, nativenames.Neo), "decimals") 447 require.NoError(t, err) 448 script = wrapDynamicScript(t, script, callflag.ReadStates) 449 e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "missing call flags") 450 }) 451 t.Run("internal state-changing call", func(t *testing.T) { 452 script, err := smartcontract.CreateCallScript(e.NativeHash(t, nativenames.Neo), "transfer", acc.ScriptHash(), acc.ScriptHash(), 1, nil) 453 require.NoError(t, err) 454 script = wrapDynamicScript(t, script, callflag.All) 455 e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "missing call flags") 456 }) 457 t.Run("exception", func(t *testing.T) { 458 script := wrapDynamicScript(t, []byte{byte(opcode.PUSH1), byte(opcode.THROW)}, callflag.ReadOnly) 459 e.InvokeScriptCheckHALT(t, script, []neotest.Signer{acc}, stackitem.Make("exception")) 460 }) 461 } 462 463 func TestGasLeft(t *testing.T) { 464 const runtimeGasLeftPrice = 1 << 4 465 466 bc, acc := chain.NewSingle(t) 467 e := neotest.NewExecutor(t, bc, acc, acc) 468 w := io.NewBufBinWriter() 469 470 gasLimit := 1100 471 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft) 472 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft) 473 require.NoError(t, w.Err) 474 tx := transaction.New(w.Bytes(), int64(gasLimit)) 475 tx.Nonce = neotest.Nonce() 476 tx.ValidUntilBlock = e.Chain.BlockHeight() + 1 477 e.SignTx(t, tx, int64(gasLimit), acc) 478 e.AddNewBlock(t, tx) 479 e.CheckHalt(t, tx.Hash()) 480 res := e.GetTxExecResult(t, tx.Hash()) 481 l1 := res.Stack[0].Value().(*big.Int) 482 l2 := res.Stack[1].Value().(*big.Int) 483 484 require.Equal(t, int64(gasLimit-runtimeGasLeftPrice*interop.DefaultBaseExecFee), l1.Int64()) 485 require.Equal(t, int64(gasLimit-2*runtimeGasLeftPrice*interop.DefaultBaseExecFee), l2.Int64()) 486 } 487 488 func TestGetAddressVersion(t *testing.T) { 489 bc, acc := chain.NewSingle(t) 490 e := neotest.NewExecutor(t, bc, acc, acc) 491 w := io.NewBufBinWriter() 492 493 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetAddressVersion) 494 require.NoError(t, w.Err) 495 e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(address.NEO3Prefix)))) 496 } 497 498 func TestGetInvocationCounter(t *testing.T) { 499 v, ic, _ := createVM(t) 500 501 cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test 502 require.NoError(t, native.PutContractState(ic.DAO, cs)) 503 504 ic.Invocations[hash.Hash160([]byte{2})] = 42 505 506 t.Run("No invocations", func(t *testing.T) { 507 v.Load([]byte{1}) 508 // do not return an error in this case. 509 require.NoError(t, runtime.GetInvocationCounter(ic)) 510 require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64()) 511 }) 512 t.Run("NonZero", func(t *testing.T) { 513 v.Load([]byte{2}) 514 require.NoError(t, runtime.GetInvocationCounter(ic)) 515 require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) 516 }) 517 t.Run("Contract", func(t *testing.T) { 518 script, err := smartcontract.CreateCallScript(cs.Hash, "invocCounter") 519 require.NoError(t, err) 520 v.LoadWithFlags(script, callflag.All) 521 require.NoError(t, v.Run()) 522 require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64()) 523 }) 524 } 525 526 func TestGetNetwork(t *testing.T) { 527 bc, acc := chain.NewSingle(t) 528 e := neotest.NewExecutor(t, bc, acc, acc) 529 w := io.NewBufBinWriter() 530 531 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetNetwork) 532 require.NoError(t, w.Err) 533 e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(bc.GetConfig().Magic)))) 534 } 535 536 func TestGetNotifications(t *testing.T) { 537 v, ic, _ := createVM(t) 538 539 ic.Notifications = []state.NotificationEvent{ 540 {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})}, 541 {ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})}, 542 {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})}, 543 } 544 545 t.Run("NoFilter", func(t *testing.T) { 546 v.Estack().PushVal(stackitem.Null{}) 547 require.NoError(t, runtime.GetNotifications(ic)) 548 549 arr := v.Estack().Pop().Array() 550 require.Equal(t, len(ic.Notifications), len(arr)) 551 for i := range arr { 552 elem := arr[i].Value().([]stackitem.Item) 553 require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value()) 554 name, err := stackitem.ToString(elem[1]) 555 require.NoError(t, err) 556 require.Equal(t, ic.Notifications[i].Name, name) 557 ic.Notifications[i].Item.MarkAsReadOnly() // tiny hack for test to be able to compare object references. 558 require.Equal(t, ic.Notifications[i].Item, elem[2]) 559 } 560 }) 561 562 t.Run("WithFilter", func(t *testing.T) { 563 h := util.Uint160{2}.BytesBE() 564 v.Estack().PushVal(h) 565 require.NoError(t, runtime.GetNotifications(ic)) 566 567 arr := v.Estack().Pop().Array() 568 require.Equal(t, 1, len(arr)) 569 elem := arr[0].Value().([]stackitem.Item) 570 require.Equal(t, h, elem[0].Value()) 571 name, err := stackitem.ToString(elem[1]) 572 require.NoError(t, err) 573 require.Equal(t, ic.Notifications[1].Name, name) 574 require.Equal(t, ic.Notifications[1].Item, elem[2]) 575 }) 576 } 577 578 func TestGetRandom_DifferentTransactions(t *testing.T) { 579 bc, acc := chain.NewSingle(t) 580 e := neotest.NewExecutor(t, bc, acc, acc) 581 582 w := io.NewBufBinWriter() 583 emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetRandom) 584 require.NoError(t, w.Err) 585 script := w.Bytes() 586 587 tx1 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1) 588 tx2 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1) 589 e.AddNewBlock(t, tx1, tx2) 590 e.CheckHalt(t, tx1.Hash()) 591 e.CheckHalt(t, tx2.Hash()) 592 593 res1 := e.GetTxExecResult(t, tx1.Hash()) 594 res2 := e.GetTxExecResult(t, tx2.Hash()) 595 596 r1, err := res1.Stack[0].TryInteger() 597 require.NoError(t, err) 598 r2, err := res2.Stack[0].TryInteger() 599 require.NoError(t, err) 600 require.NotEqual(t, r1, r2) 601 } 602 603 // Tests are taken from 604 // https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs 605 func TestGetRandomCompatibility(t *testing.T) { 606 bc, _ := chain.NewSingle(t) 607 608 b := getSharpTestGenesis(t) 609 tx := getSharpTestTx(util.Uint160{}) 610 ic, err := bc.GetTestVM(trigger.Application, tx, b) 611 require.NoError(t, err) 612 ic.Network = 860833102 // Old mainnet magic used by C# tests. 613 614 ic.VM = vm.New() 615 ic.VM.LoadScript([]byte{0x01}) 616 ic.VM.GasLimit = 1100_00000000 617 618 require.NoError(t, runtime.GetRandom(ic)) 619 require.Equal(t, "271339657438512451304577787170704246350", ic.VM.Estack().Pop().BigInt().String()) 620 621 require.NoError(t, runtime.GetRandom(ic)) 622 require.Equal(t, "98548189559099075644778613728143131367", ic.VM.Estack().Pop().BigInt().String()) 623 624 require.NoError(t, runtime.GetRandom(ic)) 625 require.Equal(t, "247654688993873392544380234598471205121", ic.VM.Estack().Pop().BigInt().String()) 626 627 require.NoError(t, runtime.GetRandom(ic)) 628 require.Equal(t, "291082758879475329976578097236212073607", ic.VM.Estack().Pop().BigInt().String()) 629 630 require.NoError(t, runtime.GetRandom(ic)) 631 require.Equal(t, "247152297361212656635216876565962360375", ic.VM.Estack().Pop().BigInt().String()) 632 } 633 634 func TestNotify(t *testing.T) { 635 caller := random.Uint160() 636 newIC := func(name string, args any) *interop.Context { 637 _, _, bc, cs := getDeployedInternal(t) 638 ic, err := bc.GetTestVM(trigger.Application, nil, nil) 639 require.NoError(t, err) 640 ic.VM.LoadNEFMethod(&cs.NEF, caller, cs.Hash, callflag.NoneFlag, true, 0, -1, nil) 641 ic.VM.Estack().PushVal(args) 642 ic.VM.Estack().PushVal(name) 643 return ic 644 } 645 t.Run("big name", func(t *testing.T) { 646 ic := newIC(string(make([]byte, runtime.MaxEventNameLen+1)), stackitem.NewArray([]stackitem.Item{stackitem.Null{}})) 647 err := runtime.Notify(ic) 648 require.Error(t, err) 649 require.True(t, strings.Contains(err.Error(), "event name must be less than 32"), err) 650 }) 651 t.Run("dynamic script", func(t *testing.T) { 652 ic := newIC("some", stackitem.Null{}) 653 ic.VM.LoadScriptWithHash([]byte{1}, random.Uint160(), callflag.NoneFlag) 654 ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(42)})) 655 ic.VM.Estack().PushVal("event") 656 err := runtime.Notify(ic) 657 require.Error(t, err) 658 require.True(t, strings.Contains(err.Error(), "notifications are not allowed in dynamic scripts"), err) 659 }) 660 t.Run("recursive struct", func(t *testing.T) { 661 arr := stackitem.NewArray([]stackitem.Item{stackitem.Null{}}) 662 arr.Append(arr) 663 ic := newIC("event", stackitem.NewArray([]stackitem.Item{arr})) // upper array is needed to match manifest event signature. 664 err := runtime.Notify(ic) 665 require.Error(t, err) 666 require.True(t, strings.Contains(err.Error(), "bad notification: recursive item"), err) 667 }) 668 t.Run("big notification", func(t *testing.T) { 669 bs := stackitem.NewByteArray(make([]byte, runtime.MaxNotificationSize+1)) 670 arr := stackitem.NewArray([]stackitem.Item{bs}) 671 ic := newIC("event", arr) 672 err := runtime.Notify(ic) 673 require.Error(t, err) 674 require.True(t, strings.Contains(err.Error(), "notification size shouldn't exceed 1024"), err) 675 }) 676 t.Run("good", func(t *testing.T) { 677 arr := stackitem.NewArray([]stackitem.Item{stackitem.Make(42)}) 678 ic := newIC("event", arr) 679 require.NoError(t, runtime.Notify(ic)) 680 require.Equal(t, 1, len(ic.Notifications)) 681 682 arr.MarkAsReadOnly() // tiny hack for test to be able to compare object references. 683 ev := ic.Notifications[0] 684 require.Equal(t, "event", ev.Name) 685 require.Equal(t, ic.VM.GetCurrentScriptHash(), ev.ScriptHash) 686 require.Equal(t, arr, ev.Item) 687 // Check deep copy. 688 arr.Value().([]stackitem.Item)[0] = stackitem.Null{} 689 require.NotEqual(t, arr, ev.Item) 690 }) 691 } 692 693 func TestSystemRuntimeNotify_HFBasilisk(t *testing.T) { 694 const ( 695 ntfName = "Hello, world!" 696 enabledHeight = 3 697 ) 698 699 bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) { 700 c.Hardforks = map[string]uint32{ 701 config.HFBasilisk.String(): enabledHeight, 702 } 703 }) 704 e := neotest.NewExecutor(t, bc, acc, acc) 705 706 script := io.NewBufBinWriter() 707 emit.Array(script.BinWriter, stackitem.Make(true)) // Boolean instead of Integer declared in manifest 708 emit.String(script.BinWriter, ntfName) 709 emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) 710 require.NoError(t, script.Err) 711 ne, err := nef.NewFile(script.Bytes()) 712 require.NoError(t, err) 713 714 m := &manifest.Manifest{ 715 Name: "ctr", 716 ABI: manifest.ABI{ 717 Methods: []manifest.Method{ 718 { 719 Name: "main", 720 Offset: 0, 721 ReturnType: smartcontract.VoidType, 722 }, 723 }, 724 Events: []manifest.Event{ 725 { 726 Name: ntfName, 727 Parameters: []manifest.Parameter{ 728 { 729 Name: "int", 730 Type: smartcontract.IntegerType, 731 }, 732 }, 733 }, 734 }, 735 }, 736 } 737 ctr := &neotest.Contract{ 738 Hash: state.CreateContractHash(e.Validator.ScriptHash(), ne.Checksum, m.Name), 739 NEF: ne, 740 Manifest: m, 741 } 742 ctrInv := e.NewInvoker(ctr.Hash, e.Validator) 743 744 // Block 0 is genesis. 745 746 // Block 1: deploy contract. 747 e.DeployContract(t, ctr, nil) 748 749 // Block 2: bad event should be logged. 750 ctrInv.Invoke(t, nil, "main") 751 752 // Block 3: bad event should fault the execution. 753 require.Equal(t, uint32(enabledHeight-1), e.Chain.BlockHeight()) 754 ctrInv.InvokeFail(t, 755 "System.Runtime.Notify failed: notification Hello, world! is invalid: parameter 0 type mismatch: Integer (manifest) vs Boolean (notification)", 756 "main") 757 }