github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/compiler_test.go (about) 1 package compiler_test 2 3 import ( 4 "fmt" 5 "math/big" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/nspcc-dev/neo-go/internal/random" 12 "github.com/nspcc-dev/neo-go/internal/versionutil" 13 "github.com/nspcc-dev/neo-go/pkg/compiler" 14 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 15 "github.com/nspcc-dev/neo-go/pkg/interop/native/neo" 16 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 17 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 18 "github.com/nspcc-dev/neo-go/pkg/util" 19 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 20 "github.com/stretchr/testify/require" 21 ) 22 23 const examplePath = "../../examples" 24 const exampleCompilePath = "testdata/compile" 25 const exampleSavePath = exampleCompilePath + "/save" 26 27 // Keep contract NEFs consistent between runs. 28 const _ = versionutil.TestVersion 29 30 type compilerTestCase struct { 31 name string 32 function func(*testing.T) 33 } 34 35 func TestCompiler(t *testing.T) { 36 testCases := []compilerTestCase{ 37 { 38 name: "TestCompileDirectory", 39 function: func(t *testing.T) { 40 const multiMainDir = "testdata/multi" 41 _, di, err := compiler.CompileWithOptions(multiMainDir, nil, nil) 42 require.NoError(t, err) 43 m := map[string]bool{} 44 for i := range di.Methods { 45 m[di.Methods[i].ID] = true 46 } 47 require.Contains(t, m, "Func1") 48 require.Contains(t, m, "Func2") 49 }, 50 }, 51 { 52 name: "TestCompile", 53 function: func(t *testing.T) { 54 infos, err := os.ReadDir(examplePath) 55 require.NoError(t, err) 56 for _, info := range infos { 57 if !info.IsDir() { 58 // example smart contracts are located in the `examplePath` subdirectories, but 59 // there is also a couple of files inside the `examplePath` which don't need to be compiled 60 continue 61 } 62 if info.Name() == "zkp" { 63 // A set of special ZKP-related examples, they have their own tests. 64 continue 65 } 66 67 targetPath := filepath.Join(examplePath, info.Name()) 68 require.NoError(t, compileFile(targetPath), info.Name()) 69 } 70 }, 71 }, 72 { 73 name: "TestCompileAndSave", 74 function: func(t *testing.T) { 75 infos, err := os.ReadDir(exampleCompilePath) 76 require.NoError(t, err) 77 err = os.MkdirAll(exampleSavePath, os.ModePerm) 78 require.NoError(t, err) 79 t.Cleanup(func() { 80 err := os.RemoveAll(exampleSavePath) 81 require.NoError(t, err) 82 }) 83 outfile := exampleSavePath + "/test.nef" 84 _, err = compiler.CompileAndSave(exampleCompilePath+"/"+infos[0].Name(), &compiler.Options{Outfile: outfile}) 85 require.NoError(t, err) 86 }, 87 }, 88 { 89 name: "TestCompileAndSave_NEF_constraints", 90 function: func(t *testing.T) { 91 tmp := t.TempDir() 92 src := `package nefconstraints 93 var data = "%s" 94 95 func Main() string { 96 return data 97 } 98 ` 99 data := make([]byte, stackitem.MaxSize-10) 100 for i := range data { 101 data[i] = byte('a') 102 } 103 in := filepath.Join(tmp, "src.go") 104 require.NoError(t, os.WriteFile(in, []byte(fmt.Sprintf(src, data)), os.ModePerm)) 105 out := filepath.Join(tmp, "test.nef") 106 _, err := compiler.CompileAndSave(in, &compiler.Options{Outfile: out}) 107 require.Error(t, err) 108 require.Contains(t, err.Error(), "serialized NEF size exceeds VM stackitem limits") 109 }, 110 }, 111 } 112 113 for _, tcase := range testCases { 114 t.Run(tcase.name, tcase.function) 115 } 116 } 117 118 func compileFile(src string) error { 119 _, err := compiler.Compile(src, nil) 120 return err 121 } 122 123 func TestOnPayableChecks(t *testing.T) { 124 compileAndCheck := func(t *testing.T, src string) error { 125 _, di, err := compiler.CompileWithOptions("payable.go", strings.NewReader(src), nil) 126 require.NoError(t, err) 127 _, err = compiler.CreateManifest(di, &compiler.Options{Name: "payable"}) 128 return err 129 } 130 131 t.Run("NEP-11, good", func(t *testing.T) { 132 src := `package payable 133 import "github.com/nspcc-dev/neo-go/pkg/interop" 134 func OnNEP11Payment(from interop.Hash160, amount int, tokenID []byte, data any) {}` 135 require.NoError(t, compileAndCheck(t, src)) 136 }) 137 t.Run("NEP-11, bad", func(t *testing.T) { 138 src := `package payable 139 import "github.com/nspcc-dev/neo-go/pkg/interop" 140 func OnNEP11Payment(from interop.Hash160, amount int, oldParam string, tokenID []byte, data any) {}` 141 require.Error(t, compileAndCheck(t, src)) 142 }) 143 t.Run("NEP-17, good", func(t *testing.T) { 144 src := `package payable 145 import "github.com/nspcc-dev/neo-go/pkg/interop" 146 func OnNEP17Payment(from interop.Hash160, amount int, data any) {}` 147 require.NoError(t, compileAndCheck(t, src)) 148 }) 149 t.Run("NEP-17, bad", func(t *testing.T) { 150 src := `package payable 151 import "github.com/nspcc-dev/neo-go/pkg/interop" 152 func OnNEP17Payment(from interop.Hash160, amount int, data any, extra int) {}` 153 require.Error(t, compileAndCheck(t, src)) 154 }) 155 } 156 157 func TestSafeMethodWarnings(t *testing.T) { 158 src := `package payable 159 func Main() int { return 1 }` 160 161 _, di, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), 162 &compiler.Options{Name: "eventTest"}) 163 require.NoError(t, err) 164 165 _, err = compiler.CreateManifest(di, &compiler.Options{SafeMethods: []string{"main"}, Name: "eventTest"}) 166 require.NoError(t, err) 167 168 _, err = compiler.CreateManifest(di, &compiler.Options{SafeMethods: []string{"main", "mississippi"}, Name: "eventTest"}) 169 require.Error(t, err) 170 } 171 172 func TestEventWarnings(t *testing.T) { 173 src := `package payable 174 import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" 175 func Main() { runtime.Notify("Event", 1) }` 176 177 _, di, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), nil) 178 require.NoError(t, err) 179 180 t.Run("event it missing from config", func(t *testing.T) { 181 _, err = compiler.CreateManifest(di, &compiler.Options{Name: "payable"}) 182 require.Error(t, err) 183 184 t.Run("suppress", func(t *testing.T) { 185 _, err = compiler.CreateManifest(di, &compiler.Options{NoEventsCheck: true, Name: "payable"}) 186 require.NoError(t, err) 187 }) 188 }) 189 t.Run("wrong parameter number", func(t *testing.T) { 190 _, err = compiler.CreateManifest(di, &compiler.Options{ 191 ContractEvents: []compiler.HybridEvent{{Name: "Event"}}, 192 Name: "payable", 193 }) 194 require.Error(t, err) 195 }) 196 t.Run("wrong parameter type", func(t *testing.T) { 197 _, err = compiler.CreateManifest(di, &compiler.Options{ 198 ContractEvents: []compiler.HybridEvent{{ 199 Name: "Event", 200 Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.StringType)}}, 201 }}, 202 Name: "payable", 203 }) 204 require.Error(t, err) 205 }) 206 t.Run("any parameter type", func(t *testing.T) { 207 _, err = compiler.CreateManifest(di, &compiler.Options{ 208 ContractEvents: []compiler.HybridEvent{{ 209 Name: "Event", 210 Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.AnyType)}}, 211 }}, 212 Name: "payable", 213 }) 214 require.NoError(t, err) 215 }) 216 t.Run("good", func(t *testing.T) { 217 _, err = compiler.CreateManifest(di, &compiler.Options{ 218 ContractEvents: []compiler.HybridEvent{{ 219 Name: "Event", 220 Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.IntegerType)}}, 221 }}, 222 Name: "payable", 223 }) 224 require.NoError(t, err) 225 }) 226 t.Run("event in imported package", func(t *testing.T) { 227 t.Run("unused", func(t *testing.T) { 228 src := `package foo 229 import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/notify" 230 func Main() int { 231 return notify.Value 232 }` 233 234 _, di, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), &compiler.Options{Name: "eventTest"}) 235 require.NoError(t, err) 236 237 _, err = compiler.CreateManifest(di, &compiler.Options{NoEventsCheck: true, Name: "eventTest"}) 238 require.NoError(t, err) 239 }) 240 t.Run("used", func(t *testing.T) { 241 src := `package foo 242 import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/notify" 243 func Main() int { 244 notify.EmitEvent() 245 return 42 246 }` 247 248 _, di, err := compiler.CompileWithOptions("eventTest.go", 249 strings.NewReader(src), &compiler.Options{Name: "eventTest"}) 250 require.NoError(t, err) 251 252 _, err = compiler.CreateManifest(di, &compiler.Options{Name: "eventTest"}) 253 require.Error(t, err) 254 255 _, err = compiler.CreateManifest(di, &compiler.Options{ 256 ContractEvents: []compiler.HybridEvent{{Name: "Event"}}, 257 Name: "eventTest", 258 }) 259 require.NoError(t, err) 260 }) 261 }) 262 t.Run("variadic event args via ellipsis", func(t *testing.T) { 263 src := `package payable 264 import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" 265 func Main() { 266 runtime.Notify("Event", []any{1}...) 267 }` 268 269 _, di, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), nil) 270 require.NoError(t, err) 271 272 _, err = compiler.CreateManifest(di, &compiler.Options{ 273 Name: "eventTest", 274 ContractEvents: []compiler.HybridEvent{{ 275 Name: "Event", 276 Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.IntegerType)}}, 277 }}, 278 }) 279 require.NoError(t, err) 280 }) 281 } 282 283 func TestNotifyInVerify(t *testing.T) { 284 srcTmpl := `package payable 285 import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" 286 func Verify() bool { runtime.%s("Event"); return true }` 287 288 for _, name := range []string{"Notify", "Log"} { 289 t.Run(name, func(t *testing.T) { 290 src := fmt.Sprintf(srcTmpl, name) 291 _, _, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), 292 &compiler.Options{ContractEvents: []compiler.HybridEvent{{Name: "Event"}}}) 293 require.Error(t, err) 294 295 t.Run("suppress", func(t *testing.T) { 296 _, _, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), 297 &compiler.Options{NoEventsCheck: true}) 298 require.NoError(t, err) 299 }) 300 }) 301 } 302 } 303 304 func TestInvokedContractsPermissons(t *testing.T) { 305 testCompile := func(t *testing.T, di *compiler.DebugInfo, disable bool, ps ...manifest.Permission) error { 306 o := &compiler.Options{ 307 NoPermissionsCheck: disable, 308 Permissions: ps, 309 Name: "test", 310 } 311 312 _, err := compiler.CreateManifest(di, o) 313 return err 314 } 315 316 t.Run("native", func(t *testing.T) { 317 src := `package test 318 import "github.com/nspcc-dev/neo-go/pkg/interop/native/neo" 319 import "github.com/nspcc-dev/neo-go/pkg/interop/native/management" 320 func Main() int { 321 neo.Transfer(nil, nil, 10, nil) 322 management.GetContract(nil) // skip read-only 323 return 0 324 }` 325 326 _, di, err := compiler.CompileWithOptions("permissionTest.go", strings.NewReader(src), nil) 327 require.NoError(t, err) 328 329 var nh util.Uint160 330 331 p := manifest.NewPermission(manifest.PermissionHash, nh) 332 require.Error(t, testCompile(t, di, false, *p)) 333 require.NoError(t, testCompile(t, di, true, *p)) 334 335 copy(nh[:], neo.Hash) 336 p.Contract.Value = nh 337 require.NoError(t, testCompile(t, di, false, *p)) 338 339 p.Methods.Restrict() 340 require.Error(t, testCompile(t, di, false, *p)) 341 require.NoError(t, testCompile(t, di, true, *p)) 342 }) 343 344 t.Run("custom", func(t *testing.T) { 345 hashStr := "aaaaaaaaaaaaaaaaaaaa" 346 src := fmt.Sprintf(`package test 347 import "github.com/nspcc-dev/neo-go/pkg/interop/contract" 348 import "github.com/nspcc-dev/neo-go/pkg/interop" 349 import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/runh" 350 351 const hash = "%s" 352 var runtimeHash interop.Hash160 353 var runtimeMethod string 354 func invoke(h string) interop.Hash160 { return nil } 355 func Main() { 356 contract.Call(interop.Hash160(hash), "method1", contract.All) 357 contract.Call(interop.Hash160(hash), "method2", contract.All) 358 contract.Call(interop.Hash160(hash), "method2", contract.All) 359 360 // skip read-only 361 contract.Call(interop.Hash160(hash), "method3", contract.ReadStates) 362 363 // skip this 364 contract.Call(interop.Hash160(hash), runtimeMethod, contract.All) 365 contract.Call(runtimeHash, "someMethod", contract.All) 366 contract.Call(interop.Hash160(runtimeHash), "someMethod", contract.All) 367 contract.Call(runh.RuntimeHash(), "method4", contract.All) 368 }`, hashStr) 369 370 _, di, err := compiler.CompileWithOptions("permissionTest.go", strings.NewReader(src), nil) 371 require.NoError(t, err) 372 373 var h util.Uint160 374 copy(h[:], hashStr) 375 376 p := manifest.NewPermission(manifest.PermissionHash, h) 377 require.NoError(t, testCompile(t, di, false, *p)) 378 379 p.Methods.Add("method1") 380 require.Error(t, testCompile(t, di, false, *p)) 381 require.NoError(t, testCompile(t, di, true, *p)) 382 383 pr := manifest.NewPermission(manifest.PermissionHash, random.Uint160()) 384 pr.Methods.Add("someMethod") 385 pr.Methods.Add("method4") 386 387 t.Run("wildcard", func(t *testing.T) { 388 pw := manifest.NewPermission(manifest.PermissionWildcard) 389 require.NoError(t, testCompile(t, di, false, *p, *pw)) 390 391 pw.Methods.Add("method2") 392 require.Error(t, testCompile(t, di, false, *p, *pw)) 393 require.NoError(t, testCompile(t, di, false, *p, *pw, *pr)) 394 }) 395 396 t.Run("group", func(t *testing.T) { 397 priv, _ := keys.NewPrivateKey() 398 pw := manifest.NewPermission(manifest.PermissionGroup, priv.PublicKey()) 399 require.NoError(t, testCompile(t, di, false, *p, *pw)) 400 401 pw.Methods.Add("invalid") 402 require.Error(t, testCompile(t, di, false, *p, *pw, *pr)) 403 404 pw.Methods.Add("method2") 405 require.Error(t, testCompile(t, di, false, *p, *pw)) 406 require.NoError(t, testCompile(t, di, false, *p, *pw, *pr)) 407 }) 408 }) 409 } 410 411 func TestUnnamedParameterCheck(t *testing.T) { 412 t.Run("single argument", func(t *testing.T) { 413 src := ` 414 package testcase 415 func Main(_ int) int { 416 x := 10 417 return x 418 } 419 ` 420 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 421 require.Error(t, err) 422 require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) 423 }) 424 t.Run("several arguments", func(t *testing.T) { 425 src := ` 426 package testcase 427 func Main(a int, b string, _ int) int { 428 x := 10 429 return x 430 } 431 ` 432 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 433 require.Error(t, err) 434 require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) 435 }) 436 t.Run("interface", func(t *testing.T) { 437 src := ` 438 package testcase 439 func OnNEP17Payment(h string, i int, _ any){} 440 ` 441 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 442 require.Error(t, err) 443 require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) 444 }) 445 t.Run("a set of unnamed params", func(t *testing.T) { 446 src := ` 447 package testcase 448 func OnNEP17Payment(_ string, _ int, _ any){} 449 ` 450 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 451 require.Error(t, err) 452 require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) 453 }) 454 t.Run("mixed named and unnamed params", func(t *testing.T) { 455 src := ` 456 package testcase 457 func OnNEP17Payment(s0, _, s2 string){} 458 ` 459 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 460 require.Error(t, err) 461 require.ErrorIs(t, err, compiler.ErrMissingExportedParamName) 462 }) 463 t.Run("empty args", func(t *testing.T) { 464 src := ` 465 package testcase 466 func OnNEP17Payment(){} 467 ` 468 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 469 require.NoError(t, err) 470 }) 471 t.Run("good", func(t *testing.T) { 472 src := ` 473 package testcase 474 func OnNEP17Payment(s string, i int, iface interface{}){} 475 ` 476 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 477 require.NoError(t, err) 478 }) 479 t.Run("good, use any keyword", func(t *testing.T) { 480 src := ` 481 package testcase 482 func OnNEP17Payment(s string, i int, iface any){} 483 ` 484 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 485 require.NoError(t, err) 486 }) 487 t.Run("method with unnamed params", func(t *testing.T) { 488 src := ` 489 package testcase 490 type A int 491 func (rsv A) OnNEP17Payment(_ string, _ int, iface any){} 492 ` 493 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 494 require.NoError(t, err) // it's OK for exported method to have unnamed params as it won't be included into manifest 495 }) 496 } 497 498 func TestReturnValuesCountCheck(t *testing.T) { 499 t.Run("void", func(t *testing.T) { 500 t.Run("exported", func(t *testing.T) { 501 t.Run("func", func(t *testing.T) { 502 src := `package testcase 503 var a int 504 func Main() { 505 a = 5 506 }` 507 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 508 require.NoError(t, err) 509 }) 510 t.Run("method", func(t *testing.T) { 511 src := `package testcase 512 type A int 513 var a int 514 func (rcv A) Main() { 515 a = 5 516 }` 517 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 518 require.NoError(t, err) 519 }) 520 }) 521 t.Run("unexported", func(t *testing.T) { 522 src := `package testcase 523 var a int 524 func main() { 525 a = 5 526 }` 527 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 528 require.NoError(t, err) 529 }) 530 }) 531 t.Run("single return", func(t *testing.T) { 532 t.Run("exported", func(t *testing.T) { 533 t.Run("func", func(t *testing.T) { 534 src := `package testcase 535 var a int 536 func Main() int { 537 a = 5 538 return a 539 }` 540 eval(t, src, big.NewInt(5)) 541 }) 542 t.Run("method", func(t *testing.T) { 543 src := `package testcase 544 type A int 545 var a int 546 func (rcv A) Main() int { 547 a = 5 548 return a 549 }` 550 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 551 require.NoError(t, err) 552 }) 553 }) 554 t.Run("unexported", func(t *testing.T) { 555 src := `package testcase 556 var a int 557 func main() int { 558 a = 5 559 return a 560 }` 561 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 562 require.NoError(t, err) 563 }) 564 }) 565 t.Run("multiple unnamed return vals", func(t *testing.T) { 566 t.Run("exported", func(t *testing.T) { 567 t.Run("func", func(t *testing.T) { 568 src := `package testcase 569 var a int 570 func Main() (int, int) { 571 a = 5 572 return a, a 573 }` 574 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 575 require.Error(t, err) 576 require.ErrorIs(t, err, compiler.ErrInvalidExportedRetCount) 577 }) 578 t.Run("method", func(t *testing.T) { 579 src := `package testcase 580 type A int 581 var a int 582 func (rcv A) Main() (int, int) { 583 a = 5 584 return a, a 585 }` 586 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 587 require.NoError(t, err) // OK for method to have multiple return values as it won't be included into manifest 588 }) 589 }) 590 t.Run("unexported", func(t *testing.T) { 591 src := `package testcase 592 var a int 593 func main() (int, int) { 594 a = 5 595 return a, a 596 }` 597 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 598 require.NoError(t, err) // OK for unexported function to have multiple return values as it won't be included into manifest 599 }) 600 }) 601 t.Run("multiple named return vals", func(t *testing.T) { 602 t.Run("exported", func(t *testing.T) { 603 t.Run("func", func(t *testing.T) { 604 src := `package testcase 605 var a int 606 func Main() (a int, b int) { 607 a = 5 608 b = 2 609 return 610 }` 611 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 612 require.Error(t, err) 613 require.ErrorIs(t, err, compiler.ErrInvalidExportedRetCount) 614 }) 615 t.Run("method", func(t *testing.T) { 616 src := `package testcase 617 type A int 618 var a int 619 func (rcv A) Main() (a int, b int) { 620 a = 5 621 b = 2 622 return 623 }` 624 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 625 require.NoError(t, err) // OK for method to have multiple return values as it won't be included into manifest 626 }) 627 }) 628 t.Run("unexported", func(t *testing.T) { 629 src := `package testcase 630 var a int 631 func main() (a int, b int) { 632 a = 5 633 b = 2 634 return 635 }` 636 _, _, err := compiler.CompileWithOptions("test.go", strings.NewReader(src), nil) 637 require.NoError(t, err) // OK for unexported function to have multiple return values as it won't be included into manifest 638 }) 639 }) 640 }