github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/cli/smartcontract/generate_test.go (about) 1 package smartcontract 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 13 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 14 "github.com/nspcc-dev/neo-go/pkg/util" 15 "github.com/stretchr/testify/require" 16 "github.com/urfave/cli" 17 ) 18 19 func TestGenerate(t *testing.T) { 20 m := manifest.NewManifest("MyContract") 21 m.ABI.Methods = append(m.ABI.Methods, 22 manifest.Method{ 23 Name: manifest.MethodDeploy, 24 ReturnType: smartcontract.VoidType, 25 }, 26 manifest.Method{ 27 Name: "sum", 28 Parameters: []manifest.Parameter{ 29 manifest.NewParameter("first", smartcontract.IntegerType), 30 manifest.NewParameter("second", smartcontract.IntegerType), 31 }, 32 ReturnType: smartcontract.IntegerType, 33 }, 34 manifest.Method{ 35 Name: "sum", // overloaded method 36 Parameters: []manifest.Parameter{ 37 manifest.NewParameter("first", smartcontract.IntegerType), 38 manifest.NewParameter("second", smartcontract.IntegerType), 39 manifest.NewParameter("third", smartcontract.IntegerType), 40 }, 41 ReturnType: smartcontract.IntegerType, 42 }, 43 manifest.Method{ 44 Name: "sum3", 45 Parameters: []manifest.Parameter{}, 46 ReturnType: smartcontract.IntegerType, 47 Safe: true, 48 }, 49 manifest.Method{ 50 Name: "zum", 51 Parameters: []manifest.Parameter{ 52 manifest.NewParameter("type", smartcontract.IntegerType), 53 manifest.NewParameter("typev", smartcontract.IntegerType), 54 manifest.NewParameter("func", smartcontract.IntegerType), 55 }, 56 ReturnType: smartcontract.IntegerType, 57 }, 58 manifest.Method{ 59 Name: "justExecute", 60 Parameters: []manifest.Parameter{ 61 manifest.NewParameter("arr", smartcontract.ArrayType), 62 }, 63 ReturnType: smartcontract.VoidType, 64 }, 65 manifest.Method{ 66 Name: "getPublicKey", 67 Parameters: nil, 68 ReturnType: smartcontract.PublicKeyType, 69 }, 70 manifest.Method{ 71 Name: "otherTypes", 72 Parameters: []manifest.Parameter{ 73 manifest.NewParameter("ctr", smartcontract.Hash160Type), 74 manifest.NewParameter("tx", smartcontract.Hash256Type), 75 manifest.NewParameter("sig", smartcontract.SignatureType), 76 manifest.NewParameter("data", smartcontract.AnyType), 77 }, 78 ReturnType: smartcontract.BoolType, 79 }, 80 manifest.Method{ 81 Name: "searchStorage", 82 Parameters: []manifest.Parameter{ 83 manifest.NewParameter("ctx", smartcontract.InteropInterfaceType), 84 }, 85 ReturnType: smartcontract.InteropInterfaceType, 86 }, 87 manifest.Method{ 88 Name: "getFromMap", 89 Parameters: []manifest.Parameter{ 90 manifest.NewParameter("intMap", smartcontract.MapType), 91 manifest.NewParameter("indices", smartcontract.ArrayType), 92 }, 93 ReturnType: smartcontract.ArrayType, 94 }, 95 manifest.Method{ 96 Name: "doSomething", 97 Parameters: []manifest.Parameter{ 98 manifest.NewParameter("bytes", smartcontract.ByteArrayType), 99 manifest.NewParameter("str", smartcontract.StringType), 100 }, 101 ReturnType: smartcontract.InteropInterfaceType, 102 }, 103 manifest.Method{ 104 Name: "getBlockWrapper", 105 Parameters: []manifest.Parameter{}, 106 ReturnType: smartcontract.InteropInterfaceType, 107 }, 108 manifest.Method{ 109 Name: "myFunc", 110 Parameters: []manifest.Parameter{ 111 manifest.NewParameter("in", smartcontract.MapType), 112 }, 113 ReturnType: smartcontract.ArrayType, 114 }) 115 116 manifestFile := filepath.Join(t.TempDir(), "manifest.json") 117 outFile := filepath.Join(t.TempDir(), "out.go") 118 119 rawManifest, err := json.Marshal(m) 120 require.NoError(t, err) 121 require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) 122 123 h := util.Uint160{ 124 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01, 125 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, 126 } 127 app := cli.NewApp() 128 app.Commands = []cli.Command{generateWrapperCmd} 129 130 rawCfg := `package: wrapper 131 hash: ` + h.StringLE() + ` 132 overrides: 133 searchStorage.ctx: storage.Context 134 searchStorage: iterator.Iterator 135 getFromMap.intMap: "map[string]int" 136 getFromMap.indices: "[]string" 137 getFromMap: "[]int" 138 getBlockWrapper: ledger.Block 139 myFunc.in: "map[int]github.com/heyitsme/mycontract.Input" 140 myFunc: "[]github.com/heyitsme/mycontract.Output" 141 callflags: 142 doSomething: ReadStates 143 ` 144 cfgPath := filepath.Join(t.TempDir(), "binding.yml") 145 require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) 146 147 require.NoError(t, app.Run([]string{"", "generate-wrapper", 148 "--manifest", manifestFile, 149 "--config", cfgPath, 150 "--out", outFile, 151 "--hash", h.StringLE(), 152 })) 153 154 const expected = `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT. 155 156 // Package wrapper contains wrappers for MyContract contract. 157 package wrapper 158 159 import ( 160 "github.com/heyitsme/mycontract" 161 "github.com/nspcc-dev/neo-go/pkg/interop" 162 "github.com/nspcc-dev/neo-go/pkg/interop/contract" 163 "github.com/nspcc-dev/neo-go/pkg/interop/iterator" 164 "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" 165 "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" 166 "github.com/nspcc-dev/neo-go/pkg/interop/storage" 167 ) 168 169 // Hash contains contract hash in big-endian form. 170 const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04" 171 172 // Sum invokes ` + "`sum`" + ` method of contract. 173 func Sum(first int, second int) int { 174 return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second).(int) 175 } 176 177 // Sum2 invokes ` + "`sum`" + ` method of contract. 178 func Sum2(first int, second int, third int) int { 179 return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second, third).(int) 180 } 181 182 // Sum3 invokes ` + "`sum3`" + ` method of contract. 183 func Sum3() int { 184 return neogointernal.CallWithToken(Hash, "sum3", int(contract.ReadOnly)).(int) 185 } 186 187 // Zum invokes ` + "`zum`" + ` method of contract. 188 func Zum(typev int, typev_ int, funcv int) int { 189 return neogointernal.CallWithToken(Hash, "zum", int(contract.All), typev, typev_, funcv).(int) 190 } 191 192 // JustExecute invokes ` + "`justExecute`" + ` method of contract. 193 func JustExecute(arr []any) { 194 neogointernal.CallWithTokenNoRet(Hash, "justExecute", int(contract.All), arr) 195 } 196 197 // GetPublicKey invokes ` + "`getPublicKey`" + ` method of contract. 198 func GetPublicKey() interop.PublicKey { 199 return neogointernal.CallWithToken(Hash, "getPublicKey", int(contract.All)).(interop.PublicKey) 200 } 201 202 // OtherTypes invokes ` + "`otherTypes`" + ` method of contract. 203 func OtherTypes(ctr interop.Hash160, tx interop.Hash256, sig interop.Signature, data any) bool { 204 return neogointernal.CallWithToken(Hash, "otherTypes", int(contract.All), ctr, tx, sig, data).(bool) 205 } 206 207 // SearchStorage invokes ` + "`searchStorage`" + ` method of contract. 208 func SearchStorage(ctx storage.Context) iterator.Iterator { 209 return neogointernal.CallWithToken(Hash, "searchStorage", int(contract.All), ctx).(iterator.Iterator) 210 } 211 212 // GetFromMap invokes ` + "`getFromMap`" + ` method of contract. 213 func GetFromMap(intMap map[string]int, indices []string) []int { 214 return neogointernal.CallWithToken(Hash, "getFromMap", int(contract.All), intMap, indices).([]int) 215 } 216 217 // DoSomething invokes ` + "`doSomething`" + ` method of contract. 218 func DoSomething(bytes []byte, str string) any { 219 return neogointernal.CallWithToken(Hash, "doSomething", int(contract.ReadStates), bytes, str).(any) 220 } 221 222 // GetBlockWrapper invokes ` + "`getBlockWrapper`" + ` method of contract. 223 func GetBlockWrapper() ledger.Block { 224 return neogointernal.CallWithToken(Hash, "getBlockWrapper", int(contract.All)).(ledger.Block) 225 } 226 227 // MyFunc invokes ` + "`myFunc`" + ` method of contract. 228 func MyFunc(in map[int]mycontract.Input) []mycontract.Output { 229 return neogointernal.CallWithToken(Hash, "myFunc", int(contract.All), in).([]mycontract.Output) 230 } 231 ` 232 233 data, err := os.ReadFile(outFile) 234 require.NoError(t, err) 235 require.Equal(t, expected, string(data)) 236 237 require.NoError(t, app.Run([]string{"", "generate-wrapper", 238 "--manifest", manifestFile, 239 "--config", cfgPath, 240 "--out", outFile, 241 })) 242 expectedWithDynamicHash := `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT. 243 244 // Package wrapper contains wrappers for MyContract contract. 245 package wrapper 246 247 import ( 248 "github.com/heyitsme/mycontract" 249 "github.com/nspcc-dev/neo-go/pkg/interop" 250 "github.com/nspcc-dev/neo-go/pkg/interop/contract" 251 "github.com/nspcc-dev/neo-go/pkg/interop/iterator" 252 "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" 253 "github.com/nspcc-dev/neo-go/pkg/interop/storage" 254 ) 255 256 // Contract represents the MyContract smart contract. 257 type Contract struct { 258 Hash interop.Hash160 259 } 260 261 // NewContract returns a new Contract instance with the specified hash. 262 func NewContract(hash interop.Hash160) Contract { 263 return Contract{Hash: hash} 264 } 265 266 // Sum invokes ` + "`sum`" + ` method of contract. 267 func (c Contract) Sum(first int, second int) int { 268 return contract.Call(c.Hash, "sum", contract.All, first, second).(int) 269 } 270 271 // Sum2 invokes ` + "`sum`" + ` method of contract. 272 func (c Contract) Sum2(first int, second int, third int) int { 273 return contract.Call(c.Hash, "sum", contract.All, first, second, third).(int) 274 } 275 276 // Sum3 invokes ` + "`sum3`" + ` method of contract. 277 func (c Contract) Sum3() int { 278 return contract.Call(c.Hash, "sum3", contract.ReadOnly).(int) 279 } 280 281 // Zum invokes ` + "`zum`" + ` method of contract. 282 func (c Contract) Zum(typev int, typev_ int, funcv int) int { 283 return contract.Call(c.Hash, "zum", contract.All, typev, typev_, funcv).(int) 284 } 285 286 // JustExecute invokes ` + "`justExecute`" + ` method of contract. 287 func (c Contract) JustExecute(arr []any) { 288 contract.Call(c.Hash, "justExecute", contract.All, arr) 289 } 290 291 // GetPublicKey invokes ` + "`getPublicKey`" + ` method of contract. 292 func (c Contract) GetPublicKey() interop.PublicKey { 293 return contract.Call(c.Hash, "getPublicKey", contract.All).(interop.PublicKey) 294 } 295 296 // OtherTypes invokes ` + "`otherTypes`" + ` method of contract. 297 func (c Contract) OtherTypes(ctr interop.Hash160, tx interop.Hash256, sig interop.Signature, data any) bool { 298 return contract.Call(c.Hash, "otherTypes", contract.All, ctr, tx, sig, data).(bool) 299 } 300 301 // SearchStorage invokes ` + "`searchStorage`" + ` method of contract. 302 func (c Contract) SearchStorage(ctx storage.Context) iterator.Iterator { 303 return contract.Call(c.Hash, "searchStorage", contract.All, ctx).(iterator.Iterator) 304 } 305 306 // GetFromMap invokes ` + "`getFromMap`" + ` method of contract. 307 func (c Contract) GetFromMap(intMap map[string]int, indices []string) []int { 308 return contract.Call(c.Hash, "getFromMap", contract.All, intMap, indices).([]int) 309 } 310 311 // DoSomething invokes ` + "`doSomething`" + ` method of contract. 312 func (c Contract) DoSomething(bytes []byte, str string) any { 313 return contract.Call(c.Hash, "doSomething", contract.ReadStates, bytes, str).(any) 314 } 315 316 // GetBlockWrapper invokes ` + "`getBlockWrapper`" + ` method of contract. 317 func (c Contract) GetBlockWrapper() ledger.Block { 318 return contract.Call(c.Hash, "getBlockWrapper", contract.All).(ledger.Block) 319 } 320 321 // MyFunc invokes ` + "`myFunc`" + ` method of contract. 322 func (c Contract) MyFunc(in map[int]mycontract.Input) []mycontract.Output { 323 return contract.Call(c.Hash, "myFunc", contract.All, in).([]mycontract.Output) 324 } 325 ` 326 data, err = os.ReadFile(outFile) 327 require.NoError(t, err) 328 require.Equal(t, expectedWithDynamicHash, string(data)) 329 } 330 331 func TestGenerateValidPackageName(t *testing.T) { 332 m := manifest.NewManifest("My space\tcontract") 333 m.ABI.Methods = append(m.ABI.Methods, 334 manifest.Method{ 335 Name: "get", 336 Parameters: []manifest.Parameter{}, 337 ReturnType: smartcontract.IntegerType, 338 Safe: true, 339 }, 340 ) 341 342 manifestFile := filepath.Join(t.TempDir(), "manifest.json") 343 outFile := filepath.Join(t.TempDir(), "out.go") 344 345 rawManifest, err := json.Marshal(m) 346 require.NoError(t, err) 347 require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) 348 349 h := util.Uint160{ 350 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01, 351 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, 352 } 353 app := cli.NewApp() 354 app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd} 355 require.NoError(t, app.Run([]string{"", "generate-wrapper", 356 "--manifest", manifestFile, 357 "--out", outFile, 358 "--hash", "0x" + h.StringLE(), 359 })) 360 361 data, err := os.ReadFile(outFile) 362 require.NoError(t, err) 363 require.Equal(t, `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT. 364 365 // Package myspacecontract contains wrappers for My space contract contract. 366 package myspacecontract 367 368 import ( 369 "github.com/nspcc-dev/neo-go/pkg/interop/contract" 370 "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" 371 ) 372 373 // Hash contains contract hash in big-endian form. 374 const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04" 375 376 // Get invokes `+"`get`"+` method of contract. 377 func Get() int { 378 return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int) 379 } 380 `, string(data)) 381 require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", 382 "--manifest", manifestFile, 383 "--out", outFile, 384 "--hash", "0x" + h.StringLE(), 385 })) 386 387 data, err = os.ReadFile(outFile) 388 require.NoError(t, err) 389 require.Equal(t, `// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT. 390 391 // Package myspacecontract contains RPC wrappers for My space contract contract. 392 package myspacecontract 393 394 import ( 395 "github.com/nspcc-dev/neo-go/pkg/neorpc/result" 396 "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" 397 "github.com/nspcc-dev/neo-go/pkg/util" 398 "math/big" 399 ) 400 401 // Hash contains contract hash. 402 var Hash = util.Uint160{0x4, 0x8, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x0, 0x1, 0xca, 0xfe, 0xba, 0xbe, 0xde, 0xad, 0xbe, 0xef, 0x3, 0x4} 403 404 // Invoker is used by ContractReader to call various safe methods. 405 type Invoker interface { 406 Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) 407 } 408 409 // ContractReader implements safe contract methods. 410 type ContractReader struct { 411 invoker Invoker 412 hash util.Uint160 413 } 414 415 // NewReader creates an instance of ContractReader using Hash and the given Invoker. 416 func NewReader(invoker Invoker) *ContractReader { 417 var hash = Hash 418 return &ContractReader{invoker, hash} 419 } 420 421 // Get invokes `+"`get`"+` method of contract. 422 func (c *ContractReader) Get() (*big.Int, error) { 423 return unwrap.BigInt(c.invoker.Call(c.hash, "get")) 424 } 425 `, string(data)) 426 } 427 428 // rewriteExpectedOutputs denotes whether expected output files should be rewritten 429 // for TestGenerateRPCBindings and TestAssistedRPCBindings. 430 const rewriteExpectedOutputs = false 431 432 func TestGenerateRPCBindings(t *testing.T) { 433 tmpDir := t.TempDir() 434 app := cli.NewApp() 435 app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd} 436 437 var checkBinding = func(manifest string, hash string, good string) { 438 t.Run(manifest, func(t *testing.T) { 439 outFile := filepath.Join(tmpDir, "out.go") 440 require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", 441 "--manifest", manifest, 442 "--out", outFile, 443 "--hash", hash, 444 })) 445 446 data, err := os.ReadFile(outFile) 447 require.NoError(t, err) 448 data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. 449 if rewriteExpectedOutputs { 450 require.NoError(t, os.WriteFile(good, data, os.ModePerm)) 451 } else { 452 expected, err := os.ReadFile(good) 453 require.NoError(t, err) 454 expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. 455 require.Equal(t, string(expected), string(data)) 456 } 457 }) 458 } 459 460 checkBinding(filepath.Join("testdata", "nex", "nex.manifest.json"), 461 "0xa2a67f09e8cf22c6bfd5cea24adc0f4bf0a11aa8", 462 filepath.Join("testdata", "nex", "nex.go")) 463 checkBinding(filepath.Join("testdata", "nameservice", "nns.manifest.json"), 464 "0x50ac1c37690cc2cfc594472833cf57505d5f46de", 465 filepath.Join("testdata", "nameservice", "nns.go")) 466 checkBinding(filepath.Join("testdata", "gas", "gas.manifest.json"), 467 "0xd2a4cff31913016155e38e474a2c06d08be276cf", 468 filepath.Join("testdata", "gas", "gas.go")) 469 checkBinding(filepath.Join("testdata", "verifyrpc", "verify.manifest.json"), 470 "0x00112233445566778899aabbccddeeff00112233", 471 filepath.Join("testdata", "verifyrpc", "verify.go")) 472 checkBinding(filepath.Join("testdata", "nonepiter", "iter.manifest.json"), 473 "0x00112233445566778899aabbccddeeff00112233", 474 filepath.Join("testdata", "nonepiter", "iter.go")) 475 476 require.False(t, rewriteExpectedOutputs) 477 } 478 479 func TestAssistedRPCBindings(t *testing.T) { 480 tmpDir := t.TempDir() 481 app := cli.NewApp() 482 app.Commands = NewCommands() 483 484 var checkBinding = func(source string, hasDefinedHash bool, guessEventTypes bool, suffix ...string) { 485 testName := source 486 if len(suffix) != 0 { 487 testName += suffix[0] 488 } 489 testName += fmt.Sprintf(", predefined hash: %t", hasDefinedHash) 490 t.Run(testName, func(t *testing.T) { 491 outFile := filepath.Join(tmpDir, "out.go") 492 configFile := filepath.Join(source, "config.yml") 493 expectedFile := filepath.Join(source, "rpcbindings.out") 494 if len(suffix) != 0 { 495 configFile = filepath.Join(source, "config"+suffix[0]+".yml") 496 expectedFile = filepath.Join(source, "rpcbindings"+suffix[0]+".out") 497 } else if !hasDefinedHash { 498 expectedFile = filepath.Join(source, "rpcbindings_dynamic_hash.out") 499 } 500 manifestF := filepath.Join(tmpDir, "manifest.json") 501 bindingF := filepath.Join(tmpDir, "binding.yml") 502 nefF := filepath.Join(tmpDir, "out.nef") 503 cmd := []string{"", "contract", "compile", 504 "--in", source, 505 "--config", configFile, 506 "--manifest", manifestF, 507 "--bindings", bindingF, 508 "--out", nefF, 509 } 510 if guessEventTypes { 511 cmd = append(cmd, "--guess-eventtypes") 512 } 513 require.NoError(t, app.Run(cmd)) 514 515 cmds := []string{"", "contract", "generate-rpcwrapper", 516 "--config", bindingF, 517 "--manifest", manifestF, 518 "--out", outFile, 519 } 520 if hasDefinedHash { 521 cmds = append(cmds, "--hash", "0x00112233445566778899aabbccddeeff00112233") 522 } 523 require.NoError(t, app.Run(cmds)) 524 525 data, err := os.ReadFile(outFile) 526 require.NoError(t, err) 527 data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. 528 if rewriteExpectedOutputs { 529 require.NoError(t, os.WriteFile(expectedFile, data, os.ModePerm)) 530 } else { 531 expected, err := os.ReadFile(expectedFile) 532 require.NoError(t, err) 533 expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. 534 require.Equal(t, string(expected), string(data)) 535 } 536 }) 537 } 538 539 for _, hasDefinedHash := range []bool{true, false} { 540 checkBinding(filepath.Join("testdata", "rpcbindings", "types"), hasDefinedHash, false) 541 checkBinding(filepath.Join("testdata", "rpcbindings", "structs"), hasDefinedHash, false) 542 } 543 checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), true, false) 544 checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), true, false, "_extended") 545 checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), true, true, "_guessed") 546 547 require.False(t, rewriteExpectedOutputs) 548 } 549 550 func TestGenerate_Errors(t *testing.T) { 551 app := cli.NewApp() 552 app.Commands = []cli.Command{generateWrapperCmd} 553 app.ExitErrHandler = func(*cli.Context, error) {} 554 555 checkError := func(t *testing.T, msg string, args ...string) { 556 // cli.ExitError doesn't implement wraping properly, so we check for an error message. 557 err := app.Run(append([]string{"", "generate-wrapper"}, args...)) 558 require.True(t, strings.Contains(err.Error(), msg), "got: %v", err) 559 } 560 t.Run("invalid hash", func(t *testing.T) { 561 checkError(t, "invalid contract hash", "--hash", "xxx", "--manifest", "yyy", "--out", "zzz") 562 }) 563 t.Run("missing manifest argument", func(t *testing.T) { 564 checkError(t, "Required flag \"manifest\" not set", "--hash", util.Uint160{}.StringLE(), "--out", "zzz") 565 }) 566 t.Run("missing manifest file", func(t *testing.T) { 567 checkError(t, "can't read contract manifest", "--manifest", "notexists", "--hash", util.Uint160{}.StringLE(), "--out", "zzz") 568 }) 569 t.Run("empty manifest", func(t *testing.T) { 570 manifestFile := filepath.Join(t.TempDir(), "invalid.json") 571 require.NoError(t, os.WriteFile(manifestFile, []byte("[]"), os.ModePerm)) 572 checkError(t, "json: cannot unmarshal array into Go value of type manifest.Manifest", "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz") 573 }) 574 t.Run("invalid manifest", func(t *testing.T) { 575 manifestFile := filepath.Join(t.TempDir(), "invalid.json") 576 m := manifest.NewManifest("MyContract") // no methods 577 rawManifest, err := json.Marshal(m) 578 require.NoError(t, err) 579 require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) 580 checkError(t, "ABI: no methods", "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz") 581 }) 582 583 manifestFile := filepath.Join(t.TempDir(), "manifest.json") 584 m := manifest.NewManifest("MyContract") 585 m.ABI.Methods = append(m.ABI.Methods, manifest.Method{ 586 Name: "method0", 587 Offset: 0, 588 ReturnType: smartcontract.AnyType, 589 Safe: true, 590 }) 591 rawManifest, err := json.Marshal(m) 592 require.NoError(t, err) 593 require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) 594 595 t.Run("missing config", func(t *testing.T) { 596 checkError(t, "can't read config file", 597 "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), 598 "--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz") 599 }) 600 t.Run("invalid config", func(t *testing.T) { 601 rawCfg := `package: wrapper 602 callflags: 603 someFunc: ReadSometimes 604 ` 605 cfgPath := filepath.Join(t.TempDir(), "binding.yml") 606 require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) 607 608 checkError(t, "can't parse config file", 609 "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), 610 "--config", cfgPath, "--out", "zzz") 611 }) 612 } 613 614 func TestCompile_GuessEventTypes(t *testing.T) { 615 app := cli.NewApp() 616 app.Commands = NewCommands() 617 app.ExitErrHandler = func(*cli.Context, error) {} 618 619 checkError := func(t *testing.T, msg string, args ...string) { 620 // cli.ExitError doesn't implement wraping properly, so we check for an error message. 621 err := app.Run(args) 622 require.Error(t, err) 623 require.True(t, strings.Contains(err.Error(), msg), "got: %v", err) 624 } 625 check := func(t *testing.T, source string, expectedErrText string) { 626 tmpDir := t.TempDir() 627 configFile := filepath.Join(source, "invalid.yml") 628 manifestF := filepath.Join(tmpDir, "invalid.manifest.json") 629 bindingF := filepath.Join(tmpDir, "invalid.binding.yml") 630 nefF := filepath.Join(tmpDir, "invalid.out.nef") 631 cmd := []string{"", "contract", "compile", 632 "--in", source, 633 "--config", configFile, 634 "--manifest", manifestF, 635 "--bindings", bindingF, 636 "--out", nefF, 637 "--guess-eventtypes", 638 } 639 checkError(t, expectedErrText, cmd...) 640 } 641 642 t.Run("not declared in manifest", func(t *testing.T) { 643 check(t, filepath.Join("testdata", "rpcbindings", "invalid1"), "inconsistent usages of event `Non declared event`: not declared in the contract config") 644 }) 645 t.Run("invalid number of params", func(t *testing.T) { 646 check(t, filepath.Join("testdata", "rpcbindings", "invalid2"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1") 647 }) 648 /* 649 // TODO: this on is a controversial one. If event information is provided in the config file, then conversion code 650 // will be emitted by the compiler according to the parameter type provided via config. Thus, we can be sure that 651 // either event parameter has the type specified in the config file or the execution of the contract will fail. 652 // Thus, this testcase is always failing (no compilation error occures). 653 // Question: do we want to compare `RealType` of the emitted parameter with the one expected in the manifest? 654 t.Run("SC parameter type mismatch", func(t *testing.T) { 655 check(t, filepath.Join("testdata", "rpcbindings", "invalid3"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1") 656 }) 657 */ 658 t.Run("extended types mismatch", func(t *testing.T) { 659 check(t, filepath.Join("testdata", "rpcbindings", "invalid4"), "inconsistent usages of event `SomeEvent`: extended type of param #0 mismatch") 660 }) 661 t.Run("named types redeclare", func(t *testing.T) { 662 check(t, filepath.Join("testdata", "rpcbindings", "invalid5"), "configured declared named type intersects with the contract's one: `invalid5.NamedStruct`") 663 }) 664 } 665 666 func TestGenerateRPCBindings_Errors(t *testing.T) { 667 app := cli.NewApp() 668 app.Commands = NewCommands() 669 app.ExitErrHandler = func(*cli.Context, error) {} 670 671 t.Run("duplicating resulting fields", func(t *testing.T) { 672 check := func(t *testing.T, packageName string, autogen bool, expectedError string) { 673 tmpDir := t.TempDir() 674 source := filepath.Join("testdata", "rpcbindings", packageName) 675 configFile := filepath.Join(source, "invalid.yml") 676 out := filepath.Join(tmpDir, "rpcbindings.out") 677 manifestF := filepath.Join(tmpDir, "manifest.json") 678 bindingF := filepath.Join(tmpDir, "binding.yml") 679 nefF := filepath.Join(tmpDir, "out.nef") 680 cmd := []string{"", "contract", "compile", 681 "--in", source, 682 "--config", configFile, 683 "--manifest", manifestF, 684 "--bindings", bindingF, 685 "--out", nefF, 686 } 687 if autogen { 688 cmd = append(cmd, "--guess-eventtypes") 689 } 690 require.NoError(t, app.Run(cmd)) 691 692 cmds := []string{"", "contract", "generate-rpcwrapper", 693 "--config", bindingF, 694 "--manifest", manifestF, 695 "--out", out, 696 } 697 err := app.Run(cmds) 698 require.Error(t, err) 699 require.True(t, strings.Contains(err.Error(), expectedError), err.Error()) 700 } 701 702 t.Run("event", func(t *testing.T) { 703 check(t, "invalid6", false, "error during generation: named type `SomeStruct` has two fields with identical resulting binding name `Field`") 704 }) 705 t.Run("autogen event", func(t *testing.T) { 706 check(t, "invalid7", true, "error during generation: named type `invalid7.SomeStruct` has two fields with identical resulting binding name `Field`") 707 }) 708 t.Run("struct", func(t *testing.T) { 709 check(t, "invalid8", false, "error during generation: named type `invalid8.SomeStruct` has two fields with identical resulting binding name `Field`") 710 }) 711 }) 712 }