github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/interop_test.go (about) 1 package compiler_test 2 3 import ( 4 "errors" 5 "fmt" 6 "math/big" 7 "strconv" 8 "strings" 9 "testing" 10 11 "github.com/nspcc-dev/neo-go/internal/fakechain" 12 "github.com/nspcc-dev/neo-go/pkg/compiler" 13 "github.com/nspcc-dev/neo-go/pkg/config" 14 "github.com/nspcc-dev/neo-go/pkg/core" 15 "github.com/nspcc-dev/neo-go/pkg/core/dao" 16 "github.com/nspcc-dev/neo-go/pkg/core/interop" 17 "github.com/nspcc-dev/neo-go/pkg/core/native" 18 "github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes" 19 "github.com/nspcc-dev/neo-go/pkg/core/state" 20 "github.com/nspcc-dev/neo-go/pkg/core/storage" 21 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 22 "github.com/nspcc-dev/neo-go/pkg/crypto/hash" 23 "github.com/nspcc-dev/neo-go/pkg/encoding/address" 24 "github.com/nspcc-dev/neo-go/pkg/encoding/base58" 25 cinterop "github.com/nspcc-dev/neo-go/pkg/interop" 26 "github.com/nspcc-dev/neo-go/pkg/neotest" 27 "github.com/nspcc-dev/neo-go/pkg/neotest/chain" 28 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 29 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 30 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 31 "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" 32 "github.com/nspcc-dev/neo-go/pkg/util" 33 "github.com/nspcc-dev/neo-go/pkg/vm" 34 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 35 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 36 "github.com/stretchr/testify/require" 37 "go.uber.org/zap/zaptest" 38 ) 39 40 func TestTypeConstantSize(t *testing.T) { 41 src := `package foo 42 import "github.com/nspcc-dev/neo-go/pkg/interop" 43 var a %T // type declaration is always ok 44 func Main() any { 45 return %#v 46 }` 47 48 t.Run("Hash160", func(t *testing.T) { 49 t.Run("good", func(t *testing.T) { 50 a := make(cinterop.Hash160, smartcontract.Hash160Len) 51 src := fmt.Sprintf(src, a, a) 52 eval(t, src, []byte(a)) 53 }) 54 t.Run("bad", func(t *testing.T) { 55 a := make(cinterop.Hash160, 19) 56 src := fmt.Sprintf(src, a, a) 57 _, err := compiler.Compile("foo.go", strings.NewReader(src)) 58 require.Error(t, err) 59 }) 60 }) 61 t.Run("Hash256", func(t *testing.T) { 62 t.Run("good", func(t *testing.T) { 63 a := make(cinterop.Hash256, smartcontract.Hash256Len) 64 src := fmt.Sprintf(src, a, a) 65 eval(t, src, []byte(a)) 66 }) 67 t.Run("bad", func(t *testing.T) { 68 a := make(cinterop.Hash256, 31) 69 src := fmt.Sprintf(src, a, a) 70 _, err := compiler.Compile("foo.go", strings.NewReader(src)) 71 require.Error(t, err) 72 }) 73 }) 74 } 75 76 func TestAddressToHash160BuiltinConversion(t *testing.T) { 77 a := "NQRLhCpAru9BjGsMwk67vdMwmzKMRgsnnN" 78 h, err := address.StringToUint160(a) 79 require.NoError(t, err) 80 a2 := "NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8" 81 addr2, err := address.StringToUint160(a2) 82 require.NoError(t, err) 83 t.Run("builtin conversion", func(t *testing.T) { 84 src := `package foo 85 import ( 86 "github.com/nspcc-dev/neo-go/pkg/interop" 87 "github.com/nspcc-dev/neo-go/pkg/interop/lib/address" 88 ) 89 var addr = address.ToHash160("` + a + `") 90 func Main() interop.Hash160 { 91 return addr 92 }` 93 prog := eval(t, src, h.BytesBE()) 94 // Address BE bytes expected to be present at program, which indicates that address conversion 95 // was performed at compile-time. 96 require.True(t, strings.Contains(string(prog), string(h.BytesBE()))) 97 // On the contrary, there should be no address string. 98 require.False(t, strings.Contains(string(prog), a)) 99 }) 100 t.Run("generate code", func(t *testing.T) { 101 src := `package foo 102 import ( 103 "github.com/nspcc-dev/neo-go/pkg/interop" 104 "github.com/nspcc-dev/neo-go/pkg/interop/lib/address" 105 ) 106 var addr = "` + a + `" 107 func Main() interop.Hash160 { 108 return address.ToHash160(addr) 109 }` 110 // Error on CALLT (std.Base58CheckDecode - method of StdLib native contract) is expected, which means 111 // that address.ToHash160 code was honestly generated by the compiler without any optimisations. 112 prog := evalWithError(t, src, "(CALLT): runtime error: invalid memory address or nil pointer dereference") 113 // Address BE bytes expected not to be present at program, which indicates that address conversion 114 // was not performed at compile-time. 115 require.False(t, strings.Contains(string(prog), string(h.BytesBE()))) 116 // On the contrary, there should be an address string. 117 require.True(t, strings.Contains(string(prog), a)) 118 }) 119 t.Run("AliasPackage", func(t *testing.T) { 120 src := ` 121 package foo 122 import ad "github.com/nspcc-dev/neo-go/pkg/interop/lib/address" 123 func Main() []byte { 124 addr1 := ad.ToHash160("` + a + `") 125 addr2 := ad.ToHash160("` + a2 + `") 126 sum := append(addr1, addr2...) 127 return sum 128 }` 129 eval(t, src, append(h.BytesBE(), addr2.BytesBE()...)) 130 }) 131 } 132 133 func TestInvokeAddressToFromHash160(t *testing.T) { 134 a := "NQRLhCpAru9BjGsMwk67vdMwmzKMRgsnnN" 135 h, err := address.StringToUint160(a) 136 require.NoError(t, err) 137 138 bc, acc := chain.NewSingle(t) 139 e := neotest.NewExecutor(t, bc, acc, acc) 140 src := `package foo 141 import ( 142 "github.com/nspcc-dev/neo-go/pkg/interop" 143 "github.com/nspcc-dev/neo-go/pkg/interop/lib/address" 144 ) 145 const addr = "` + a + `" 146 func ToHash160(a string) interop.Hash160 { 147 return address.ToHash160(a) 148 } 149 func ToHash160AtCompileTime() interop.Hash160 { 150 return address.ToHash160(addr) 151 } 152 func FromHash160(hash interop.Hash160) string { 153 return address.FromHash160(hash) 154 }` 155 ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{Name: "Helper"}) 156 e.DeployContract(t, ctr, nil) 157 c := e.CommitteeInvoker(ctr.Hash) 158 159 t.Run("ToHash160", func(t *testing.T) { 160 t.Run("invalid address length", func(t *testing.T) { 161 c.InvokeFail(t, "invalid address length", "toHash160", base58.CheckEncode(make([]byte, util.Uint160Size+1+1))) 162 }) 163 t.Run("invalid prefix", func(t *testing.T) { 164 c.InvokeFail(t, "invalid address prefix", "toHash160", base58.CheckEncode(append([]byte{address.NEO2Prefix}, h.BytesBE()...))) 165 }) 166 t.Run("good", func(t *testing.T) { 167 c.Invoke(t, stackitem.NewBuffer(h.BytesBE()), "toHash160", a) 168 }) 169 }) 170 t.Run("ToHash160Constant", func(t *testing.T) { 171 t.Run("good", func(t *testing.T) { 172 c.Invoke(t, stackitem.NewBuffer(h.BytesBE()), "toHash160AtCompileTime") 173 }) 174 }) 175 t.Run("FromHash160", func(t *testing.T) { 176 t.Run("good", func(t *testing.T) { 177 c.Invoke(t, stackitem.NewByteArray([]byte(a)), "fromHash160", h.BytesBE()) 178 }) 179 t.Run("invalid length", func(t *testing.T) { 180 c.InvokeFail(t, "invalid Hash160 length", "fromHash160", h.BytesBE()[:15]) 181 }) 182 }) 183 } 184 185 func TestAbort(t *testing.T) { 186 src := `package foo 187 import "github.com/nspcc-dev/neo-go/pkg/interop/util" 188 func Main() int { 189 util.Abort() 190 return 1 191 }` 192 v := vmAndCompile(t, src) 193 require.Error(t, v.Run()) 194 require.True(t, v.HasFailed()) 195 } 196 197 func TestAbortMsg(t *testing.T) { 198 src := `package foo 199 import "github.com/nspcc-dev/neo-go/pkg/interop/util" 200 func Main() int { 201 util.AbortMsg("some message") 202 return 1 203 }` 204 v := vmAndCompile(t, src) 205 err := v.Run() 206 require.Error(t, err) 207 require.True(t, v.HasFailed()) 208 require.True(t, strings.Contains(err.Error(), "ABORTMSG is executed. Reason: some message"), err) 209 } 210 211 func TestAssert(t *testing.T) { 212 src := `package foo 213 import "github.com/nspcc-dev/neo-go/pkg/interop/util" 214 func Main(ok bool) int { 215 util.Assert(ok) 216 return 1 217 }` 218 219 // assert OK 220 evalWithArgs(t, src, nil, []stackitem.Item{stackitem.Make(true)}, big.NewInt(1)) 221 222 // assert FALSE 223 v := vmAndCompile(t, src) 224 v.Estack().PushVal(false) 225 err := v.Run() 226 require.Error(t, err) 227 require.True(t, v.HasFailed()) 228 require.True(t, strings.Contains(err.Error(), "ASSERT"), err) 229 } 230 231 func TestAssertMsg(t *testing.T) { 232 src := `package foo 233 import "github.com/nspcc-dev/neo-go/pkg/interop/util" 234 func Main(ok bool) int { 235 util.AssertMsg(ok, "some message") 236 return 1 237 }` 238 239 // assert OK 240 evalWithArgs(t, src, nil, []stackitem.Item{stackitem.Make(true)}, big.NewInt(1)) 241 242 // assert FALSE 243 v := vmAndCompile(t, src) 244 v.Estack().PushVal(false) 245 err := v.Run() 246 require.Error(t, err) 247 require.True(t, v.HasFailed()) 248 require.True(t, strings.Contains(err.Error(), "ASSERTMSG is executed with false result. Reason: some message"), err) 249 } 250 251 func TestCurrentSigners(t *testing.T) { 252 bc, acc := chain.NewSingle(t) 253 e := neotest.NewExecutor(t, bc, acc, acc) 254 src := `package foo 255 import ( 256 "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" 257 "github.com/nspcc-dev/neo-go/pkg/interop/runtime" 258 ) 259 func Main() []ledger.TransactionSigner { 260 return runtime.CurrentSigners() 261 }` 262 ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{Name: "Helper"}) 263 e.DeployContract(t, ctr, nil) 264 c := e.CommitteeInvoker(ctr.Hash) 265 266 t.Run("non-empty", func(t *testing.T) { 267 expected := stackitem.NewArray([]stackitem.Item{ 268 stackitem.NewArray([]stackitem.Item{ 269 stackitem.NewByteArray(e.CommitteeHash.BytesBE()), 270 stackitem.NewBigInteger(big.NewInt(int64(transaction.Global))), 271 stackitem.NewArray([]stackitem.Item{}), 272 stackitem.NewArray([]stackitem.Item{}), 273 stackitem.NewArray([]stackitem.Item{}), 274 }), 275 }) 276 c.Invoke(t, expected, "main") 277 }) 278 } 279 280 func TestStdLib_StrLen(t *testing.T) { 281 bc, acc := chain.NewSingle(t) 282 e := neotest.NewExecutor(t, bc, acc, acc) 283 src := `package foo 284 import ( 285 "github.com/nspcc-dev/neo-go/pkg/interop/native/std" 286 ) 287 func Main(s string) int { 288 return std.StrLen(s) 289 }` 290 ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{Name: "Helper"}) 291 e.DeployContract(t, ctr, nil) 292 c := e.CommitteeInvoker(ctr.Hash) 293 294 expected := stackitem.Make(1) 295 c.Invoke(t, expected, "main", "🦆") 296 c.Invoke(t, expected, "main", "ã") 297 c.Invoke(t, expected, "main", "a") 298 299 expected = stackitem.Make(7) 300 c.Invoke(t, expected, "main", "abc 123") 301 } 302 303 func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM { 304 b, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil) 305 require.NoError(t, err) 306 v := core.SpawnVM(ic) 307 invokeMethod(t, testMainIdent, b.Script, v, di) 308 v.LoadScriptWithFlags(b.Script, callflag.All) 309 return v 310 } 311 312 func TestAppCall(t *testing.T) { 313 srcDeep := `package foo 314 func Get42() int { 315 return 42 316 }` 317 barCtr, di, err := compiler.CompileWithOptions("bar.go", strings.NewReader(srcDeep), nil) 318 require.NoError(t, err) 319 mBar, err := di.ConvertToManifest(&compiler.Options{Name: "Bar"}) 320 require.NoError(t, err) 321 322 barH := hash.Hash160(barCtr.Script) 323 324 srcInner := `package foo 325 import "github.com/nspcc-dev/neo-go/pkg/interop/contract" 326 import "github.com/nspcc-dev/neo-go/pkg/interop" 327 var a int = 3 328 func Main(a []byte, b []byte) []byte { 329 panic("Main was called") 330 } 331 func Append(a []byte, b []byte) []byte { 332 return append(a, b...) 333 } 334 func Add3(n int) int { 335 return a + n 336 } 337 func CallInner() int { 338 return contract.Call(%s, "get42", contract.All).(int) 339 }` 340 srcInner = fmt.Sprintf(srcInner, 341 fmt.Sprintf("%#v", cinterop.Hash160(barH.BytesBE()))) 342 343 inner, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(srcInner), nil) 344 require.NoError(t, err) 345 m, err := di.ConvertToManifest(&compiler.Options{ 346 Name: "Foo", 347 Permissions: []manifest.Permission{ 348 *manifest.NewPermission(manifest.PermissionWildcard), 349 }, 350 }) 351 require.NoError(t, err) 352 353 ih := hash.Hash160(inner.Script) 354 var contractGetter = func(_ *dao.Simple, h util.Uint160) (*state.Contract, error) { 355 if h.Equals(ih) { 356 return &state.Contract{ 357 ContractBase: state.ContractBase{ 358 Hash: ih, 359 NEF: *inner, 360 Manifest: *m, 361 }, 362 }, nil 363 } else if h.Equals(barH) { 364 return &state.Contract{ 365 ContractBase: state.ContractBase{ 366 Hash: barH, 367 NEF: *barCtr, 368 Manifest: *mBar, 369 }, 370 }, nil 371 } 372 return nil, errors.New("not found") 373 } 374 375 fc := fakechain.NewFakeChain() 376 ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false), 377 interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, nil, zaptest.NewLogger(t)) 378 379 t.Run("valid script", func(t *testing.T) { 380 src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) 381 v := spawnVM(t, ic, src) 382 require.NoError(t, v.Run()) 383 384 assertResult(t, v, []byte{1, 2, 3, 4}) 385 }) 386 387 t.Run("callEx, valid", func(t *testing.T) { 388 src := getCallExScript(fmt.Sprintf("%#v", ih.BytesBE()), "contract.ReadStates|contract.AllowCall") 389 v := spawnVM(t, ic, src) 390 require.NoError(t, v.Run()) 391 392 assertResult(t, v, big.NewInt(42)) 393 }) 394 t.Run("callEx, missing flags", func(t *testing.T) { 395 src := getCallExScript(fmt.Sprintf("%#v", ih.BytesBE()), "contract.NoneFlag") 396 v := spawnVM(t, ic, src) 397 require.Error(t, v.Run()) 398 }) 399 400 t.Run("missing script", func(t *testing.T) { 401 h := ih 402 h[0] = ^h[0] 403 404 src := getAppCallScript(fmt.Sprintf("%#v", h.BytesBE())) 405 v := spawnVM(t, ic, src) 406 require.Error(t, v.Run()) 407 }) 408 409 t.Run("convert from string constant", func(t *testing.T) { 410 src := ` 411 package foo 412 import "github.com/nspcc-dev/neo-go/pkg/interop/contract" 413 const scriptHash = ` + fmt.Sprintf("%#v", string(ih.BytesBE())) + ` 414 func Main() []byte { 415 x := []byte{1, 2} 416 y := []byte{3, 4} 417 result := contract.Call([]byte(scriptHash), "append", contract.All, x, y) 418 return result.([]byte) 419 } 420 ` 421 422 v := spawnVM(t, ic, src) 423 require.NoError(t, v.Run()) 424 425 assertResult(t, v, []byte{1, 2, 3, 4}) 426 }) 427 428 t.Run("convert from var", func(t *testing.T) { 429 src := ` 430 package foo 431 import "github.com/nspcc-dev/neo-go/pkg/interop/contract" 432 func Main() []byte { 433 x := []byte{1, 2} 434 y := []byte{3, 4} 435 var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `) 436 result := contract.Call(addr, "append", contract.All, x, y) 437 return result.([]byte) 438 } 439 ` 440 441 v := spawnVM(t, ic, src) 442 require.NoError(t, v.Run()) 443 444 assertResult(t, v, []byte{1, 2, 3, 4}) 445 }) 446 447 t.Run("InitializedGlobals", func(t *testing.T) { 448 src := `package foo 449 import "github.com/nspcc-dev/neo-go/pkg/interop/contract" 450 func Main() int { 451 var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `) 452 result := contract.Call(addr, "add3", contract.All, 39) 453 return result.(int) 454 }` 455 456 v := spawnVM(t, ic, src) 457 require.NoError(t, v.Run()) 458 459 assertResult(t, v, big.NewInt(42)) 460 }) 461 462 t.Run("AliasPackage", func(t *testing.T) { 463 src := `package foo 464 import ee "github.com/nspcc-dev/neo-go/pkg/interop/contract" 465 func Main() int { 466 var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `) 467 result := ee.Call(addr, "add3", ee.All, 39) 468 return result.(int) 469 }` 470 v := spawnVM(t, ic, src) 471 require.NoError(t, v.Run()) 472 assertResult(t, v, big.NewInt(42)) 473 }) 474 } 475 476 func getAppCallScript(h string) string { 477 return ` 478 package foo 479 import "github.com/nspcc-dev/neo-go/pkg/interop/contract" 480 func Main() []byte { 481 x := []byte{1, 2} 482 y := []byte{3, 4} 483 result := contract.Call(` + h + `, "append", contract.All, x, y) 484 return result.([]byte) 485 } 486 ` 487 } 488 489 func getCallExScript(h string, flags string) string { 490 return `package foo 491 import "github.com/nspcc-dev/neo-go/pkg/interop/contract" 492 func Main() int { 493 result := contract.Call(` + h + `, "callInner", ` + flags + `) 494 return result.(int) 495 }` 496 } 497 498 func TestBuiltinDoesNotCompile(t *testing.T) { 499 src := `package foo 500 import "github.com/nspcc-dev/neo-go/pkg/interop/util" 501 func Main() bool { 502 a := 1 503 b := 2 504 return util.Equals(a, b) 505 }` 506 507 v := vmAndCompile(t, src) 508 ctx := v.Context() 509 retCount := 0 510 for op, _, err := ctx.Next(); err == nil; op, _, err = ctx.Next() { 511 if ctx.IP() >= len(ctx.Program()) { 512 break 513 } 514 if op == opcode.RET { 515 retCount++ 516 } 517 } 518 require.Equal(t, 1, retCount) 519 } 520 521 func TestInteropPackage(t *testing.T) { 522 src := `package foo 523 import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/block" 524 func Main() int { 525 b := block.Block{} 526 a := block.GetTransactionCount(b) 527 return a 528 }` 529 eval(t, src, big.NewInt(42)) 530 } 531 532 func TestBuiltinPackage(t *testing.T) { 533 src := `package foo 534 import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/util" 535 func Main() int { 536 if util.Equals(1, 2) { // always returns true 537 return 1 538 } 539 return 2 540 }` 541 eval(t, src, big.NewInt(1)) 542 } 543 544 func TestLenForNil(t *testing.T) { 545 src := ` 546 package foo 547 func Main() bool { 548 var a []int = nil 549 return len(a) == 0 550 }` 551 552 eval(t, src, true) 553 } 554 555 func TestCallTConversionErrors(t *testing.T) { 556 t.Run("variable hash", func(t *testing.T) { 557 src := `package foo 558 import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" 559 func Main() int { 560 var hash string 561 return neogointernal.CallWithToken(hash, "method", 0).(int) 562 }` 563 _, err := compiler.Compile("foo.go", strings.NewReader(src)) 564 require.Error(t, err) 565 }) 566 t.Run("bad hash", func(t *testing.T) { 567 src := `package foo 568 import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" 569 func Main() int { 570 return neogointernal.CallWithToken("badstring", "method", 0).(int) 571 }` 572 _, err := compiler.Compile("foo.go", strings.NewReader(src)) 573 require.Error(t, err) 574 }) 575 t.Run("variable method", func(t *testing.T) { 576 src := `package foo 577 import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" 578 func Main() int { 579 var method string 580 return neogointernal.CallWithToken("\xf5\x63\xea\x40\xbc\x28\x3d\x4d\x0e\x05\xc4\x8e\xa3\x05\xb3\xf2\xa0\x73\x40\xef", method, 0).(int) 581 }` 582 _, err := compiler.Compile("foo.go", strings.NewReader(src)) 583 require.Error(t, err) 584 }) 585 t.Run("variable flags", func(t *testing.T) { 586 src := `package foo 587 import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" 588 func Main() { 589 var flags int 590 neogointernal.CallWithTokenNoRet("\xf5\x63\xea\x40\xbc\x28\x3d\x4d\x0e\x05\xc4\x8e\xa3\x05\xb3\xf2\xa0\x73\x40\xef", "method", flags) 591 }` 592 _, err := compiler.Compile("foo.go", strings.NewReader(src)) 593 require.Error(t, err) 594 }) 595 } 596 597 func TestCallWithVersion(t *testing.T) { 598 bc, acc := chain.NewSingle(t) 599 e := neotest.NewExecutor(t, bc, acc, acc) 600 src := `package foo 601 import ( 602 "github.com/nspcc-dev/neo-go/pkg/interop" 603 "github.com/nspcc-dev/neo-go/pkg/interop/contract" 604 util "github.com/nspcc-dev/neo-go/pkg/interop/lib/contract" 605 ) 606 func CallWithVersion(hash interop.Hash160, version int, method string) any { 607 return util.CallWithVersion(hash, version, method, contract.All) 608 }` 609 ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{Name: "Helper"}) 610 e.DeployContract(t, ctr, nil) 611 c := e.CommitteeInvoker(ctr.Hash) 612 613 policyH := nativehashes.PolicyContract 614 t.Run("good", func(t *testing.T) { 615 c.Invoke(t, e.Chain.GetBaseExecFee(), "callWithVersion", policyH.BytesBE(), 0, "getExecFeeFactor") 616 }) 617 t.Run("unknown contract", func(t *testing.T) { 618 c.InvokeFail(t, "unknown contract", "callWithVersion", util.Uint160{1, 2, 3}.BytesBE(), 0, "getExecFeeFactor") 619 }) 620 t.Run("invalid version", func(t *testing.T) { 621 c.InvokeFail(t, "contract version mismatch", "callWithVersion", policyH.BytesBE(), 1, "getExecFeeFactor") 622 }) 623 } 624 625 func TestForcedNotifyArgumentsConversion(t *testing.T) { 626 const methodWithEllipsis = "withEllipsis" 627 const methodWithoutEllipsis = "withoutEllipsis" 628 check := func(t *testing.T, method string, targetSCParamTypes []smartcontract.ParamType, expectedVMParamTypes []stackitem.Type, noEventsCheck bool) { 629 bc, acc := chain.NewSingleWithCustomConfig(t, func(blockchain *config.Blockchain) { 630 blockchain.Hardforks = map[string]uint32{config.HFBasilisk.String(): 100500} // Disable runtime notifications check to reuse the same contract for different event parameter types. 631 }) 632 e := neotest.NewExecutor(t, bc, acc, acc) 633 src := `package foo 634 import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" 635 const arg4 = 4 // Const value. 636 func WithoutEllipsis() { 637 var arg0 int // Default value. 638 var arg1 int = 1 // Initialized value. 639 arg2 := 2 // Short decl. 640 var arg3 int 641 arg3 = 3 // Declare first, change value afterwards. 642 runtime.Notify("withoutEllipsis", arg0, arg1, arg2, arg3, arg4, 5, f(6)) // The fifth argument is basic literal. 643 } 644 func WithEllipsis() { 645 arg := []any{0, 1, f(2), 3, 4, 5, 6} 646 runtime.Notify("withEllipsis", arg...) 647 } 648 func f(i int) int { 649 return i 650 }` 651 count := len(targetSCParamTypes) 652 if count != len(expectedVMParamTypes) { 653 t.Fatalf("parameters count mismatch: %d vs %d", count, len(expectedVMParamTypes)) 654 } 655 scParams := make([]compiler.HybridParameter, len(targetSCParamTypes)) 656 vmParams := make([]stackitem.Item, len(expectedVMParamTypes)) 657 for i := range scParams { 658 scParams[i] = compiler.HybridParameter{Parameter: manifest.Parameter{ 659 Name: strconv.Itoa(i), 660 Type: targetSCParamTypes[i], 661 }} 662 defaultValue := stackitem.NewBigInteger(big.NewInt(int64(i))) 663 var ( 664 val stackitem.Item 665 err error 666 ) 667 if expectedVMParamTypes[i] == stackitem.IntegerT { 668 val = defaultValue 669 } else { 670 val, err = defaultValue.Convert(expectedVMParamTypes[i]) // exactly the same conversion should be emitted by compiler and performed by the contract code. 671 require.NoError(t, err) 672 } 673 vmParams[i] = val 674 } 675 ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{ 676 Name: "Helper", 677 ContractEvents: []compiler.HybridEvent{ 678 { 679 Name: methodWithoutEllipsis, 680 Parameters: scParams, 681 }, 682 { 683 Name: methodWithEllipsis, 684 Parameters: scParams, 685 }, 686 }, 687 NoEventsCheck: noEventsCheck, 688 }) 689 e.DeployContract(t, ctr, nil) 690 c := e.CommitteeInvoker(ctr.Hash) 691 692 t.Run(method, func(t *testing.T) { 693 h := c.Invoke(t, stackitem.Null{}, method) 694 aer := c.GetTxExecResult(t, h) 695 require.Equal(t, 1, len(aer.Events)) 696 require.Equal(t, stackitem.NewArray(vmParams), aer.Events[0].Item) 697 }) 698 } 699 checkSingleType := func(t *testing.T, method string, targetSCEventType smartcontract.ParamType, expectedVMType stackitem.Type, noEventsCheck ...bool) { 700 count := 7 701 scParams := make([]smartcontract.ParamType, count) 702 vmParams := make([]stackitem.Type, count) 703 for i := range scParams { 704 scParams[i] = targetSCEventType 705 vmParams[i] = expectedVMType 706 } 707 var noEvents bool 708 if len(noEventsCheck) > 0 { 709 noEvents = noEventsCheck[0] 710 } 711 check(t, method, scParams, vmParams, noEvents) 712 } 713 714 t.Run("good, single type, default values", func(t *testing.T) { 715 checkSingleType(t, methodWithoutEllipsis, smartcontract.IntegerType, stackitem.IntegerT) 716 }) 717 t.Run("good, single type, conversion to BooleanT", func(t *testing.T) { 718 checkSingleType(t, methodWithoutEllipsis, smartcontract.BoolType, stackitem.BooleanT) 719 }) 720 t.Run("good, single type, Hash160Type->ByteArray", func(t *testing.T) { 721 checkSingleType(t, methodWithoutEllipsis, smartcontract.Hash160Type, stackitem.ByteArrayT) 722 }) 723 t.Run("good, single type, Hash256Type->ByteArray", func(t *testing.T) { 724 checkSingleType(t, methodWithoutEllipsis, smartcontract.Hash256Type, stackitem.ByteArrayT) 725 }) 726 t.Run("good, single type, Signature->ByteArray", func(t *testing.T) { 727 checkSingleType(t, methodWithoutEllipsis, smartcontract.SignatureType, stackitem.ByteArrayT) 728 }) 729 t.Run("good, single type, String->ByteArray", func(t *testing.T) { 730 checkSingleType(t, methodWithoutEllipsis, smartcontract.StringType, stackitem.ByteArrayT) // Special case, runtime.Notify will convert any Buffer to ByteArray. 731 }) 732 t.Run("good, single type, PublicKeyType->ByteArray", func(t *testing.T) { 733 checkSingleType(t, methodWithoutEllipsis, smartcontract.PublicKeyType, stackitem.ByteArrayT) 734 }) 735 t.Run("good, single type, AnyType->do not change initial type", func(t *testing.T) { 736 checkSingleType(t, methodWithoutEllipsis, smartcontract.AnyType, stackitem.IntegerT) // Special case, compiler should leave the type "as is" and do not emit conversion code. 737 }) 738 // Test for InteropInterface->... is missing, because we don't enforce conversion to stackitem.InteropInterface, 739 // but compiler still checks these notifications against expected manifest. 740 t.Run("good, multiple types, check the conversion order", func(t *testing.T) { 741 check(t, methodWithoutEllipsis, []smartcontract.ParamType{ 742 smartcontract.IntegerType, 743 smartcontract.BoolType, 744 smartcontract.ByteArrayType, 745 smartcontract.PublicKeyType, 746 smartcontract.Hash160Type, 747 smartcontract.AnyType, // leave initial type 748 smartcontract.StringType, 749 }, []stackitem.Type{ 750 stackitem.IntegerT, 751 stackitem.BooleanT, 752 stackitem.ByteArrayT, 753 stackitem.ByteArrayT, 754 stackitem.ByteArrayT, 755 stackitem.IntegerT, // leave initial type 756 stackitem.ByteArrayT, 757 }, false) 758 }) 759 t.Run("with ellipsis, do not emit conversion code", func(t *testing.T) { 760 checkSingleType(t, methodWithEllipsis, smartcontract.IntegerType, stackitem.IntegerT) 761 checkSingleType(t, methodWithEllipsis, smartcontract.BoolType, stackitem.IntegerT) 762 checkSingleType(t, methodWithEllipsis, smartcontract.ByteArrayType, stackitem.IntegerT) 763 }) 764 t.Run("no events check => no conversion code", func(t *testing.T) { 765 checkSingleType(t, methodWithoutEllipsis, smartcontract.PublicKeyType, stackitem.IntegerT, true) 766 }) 767 }