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  }