github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/internal/contracts/contracts_test.go (about)

     1  package contracts
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/nspcc-dev/neo-go/pkg/config"
     9  	"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    12  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    13  	"github.com/nspcc-dev/neo-go/pkg/io"
    14  	"github.com/nspcc-dev/neo-go/pkg/neotest"
    15  	"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
    16  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    17  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    18  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    19  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
    20  	"github.com/nspcc-dev/neo-go/pkg/util"
    21  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    22  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    23  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  // TestGenerateHelperContracts generates contract states that are used in tests.
    28  // See generateOracleContract and generateManagementHelperContracts comments for
    29  // details.
    30  func TestGenerateHelperContracts(t *testing.T) {
    31  	const saveState = false
    32  
    33  	generateOracleContract(t, saveState)
    34  	generateManagementHelperContracts(t, saveState)
    35  
    36  	require.False(t, saveState)
    37  }
    38  
    39  // generateOracleContract generates a helper contract that is able to call
    40  // the native Oracle contract and has callback method. It uses testchain to define
    41  // Oracle and StdLib native hashes and saves the generated NEF and manifest to `oracle_contract` folder.
    42  // Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
    43  func generateOracleContract(t *testing.T, saveState bool) {
    44  	ctr := neotest.CompileFile(t, util.Uint160{}, oracleContractModPath, oracleContractYAMLPath)
    45  
    46  	// Write NEF file.
    47  	bytes, err := ctr.NEF.Bytes()
    48  	require.NoError(t, err)
    49  	if saveState {
    50  		err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
    51  		require.NoError(t, err)
    52  	}
    53  
    54  	// Write manifest file.
    55  	mData, err := json.Marshal(ctr.Manifest)
    56  	require.NoError(t, err)
    57  	if saveState {
    58  		err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
    59  		require.NoError(t, err)
    60  	}
    61  }
    62  
    63  // generateManagementHelperContracts generates 2 helper contracts, second of which is
    64  // allowed to call the first. It uses testchain to define Management and StdLib
    65  // native hashes and saves the generated NEF and manifest to `management_contract` folder.
    66  // Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
    67  func generateManagementHelperContracts(t *testing.T, saveState bool) {
    68  	bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
    69  		c.P2PSigExtensions = true
    70  	})
    71  	e := neotest.NewExecutor(t, bc, validator, committee)
    72  
    73  	mgmtHash := e.NativeHash(t, nativenames.Management)
    74  	stdHash := e.NativeHash(t, nativenames.StdLib)
    75  	neoHash := e.NativeHash(t, nativenames.Neo)
    76  	singleChainValidatorAcc := e.Validator.(neotest.MultiSigner).Single(2).Account() // priv0
    77  	require.NoError(t, singleChainValidatorAcc.ConvertMultisig(1, keys.PublicKeys{singleChainValidatorAcc.PublicKey()}))
    78  	singleChainValidatorHash := singleChainValidatorAcc.Contract.ScriptHash()
    79  
    80  	w := io.NewBufBinWriter()
    81  	emit.Opcodes(w.BinWriter, opcode.ABORT)
    82  	addOff := w.Len()
    83  	emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET)
    84  	addMultiOff := w.Len()
    85  	emit.Opcodes(w.BinWriter, opcode.ADD, opcode.ADD, opcode.RET)
    86  	ret7Off := w.Len()
    87  	emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET)
    88  	dropOff := w.Len()
    89  	emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET)
    90  	initOff := w.Len()
    91  	emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET)
    92  	add3Off := w.Len()
    93  	emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET)
    94  	invalidRetOff := w.Len()
    95  	emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET)
    96  	justRetOff := w.Len()
    97  	emit.Opcodes(w.BinWriter, opcode.RET)
    98  	verifyOff := w.Len()
    99  	emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB,
   100  		opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET)
   101  	deployOff := w.Len()
   102  	emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3)
   103  	emit.String(w.BinWriter, "create")                                  // 8 bytes
   104  	emit.Int(w.BinWriter, 2)                                            // 1 byte
   105  	emit.Opcodes(w.BinWriter, opcode.PACK)                              // 1 byte
   106  	emit.Int(w.BinWriter, 1)                                            // 1 byte (args count for `serialize`)
   107  	emit.Opcodes(w.BinWriter, opcode.PACK)                              // 1 byte (pack args into array for `serialize`)
   108  	emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
   109  	emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+3, opcode.RET)
   110  	emit.String(w.BinWriter, "update")                                  // 8 bytes
   111  	emit.Int(w.BinWriter, 2)                                            // 1 byte
   112  	emit.Opcodes(w.BinWriter, opcode.PACK)                              // 1 byte
   113  	emit.Int(w.BinWriter, 1)                                            // 1 byte (args count for `serialize`)
   114  	emit.Opcodes(w.BinWriter, opcode.PACK)                              // 1 byte (pack args into array for `serialize`)
   115  	emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
   116  	emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET)
   117  	putValOff := w.Len()
   118  	emit.String(w.BinWriter, "initial")
   119  	emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
   120  	emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
   121  	emit.Opcodes(w.BinWriter, opcode.RET)
   122  	getValOff := w.Len()
   123  	emit.String(w.BinWriter, "initial")
   124  	emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
   125  	emit.Syscall(w.BinWriter, interopnames.SystemStorageGet)
   126  	emit.Opcodes(w.BinWriter, opcode.RET)
   127  	delValOff := w.Len()
   128  	emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
   129  	emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete)
   130  	emit.Opcodes(w.BinWriter, opcode.RET)
   131  	onNEP17PaymentOff := w.Len()
   132  	emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
   133  	emit.Int(w.BinWriter, 4)
   134  	emit.Opcodes(w.BinWriter, opcode.PACK)
   135  	emit.String(w.BinWriter, "LastPaymentNEP17")
   136  	emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
   137  	emit.Opcodes(w.BinWriter, opcode.RET)
   138  	onNEP11PaymentOff := w.Len()
   139  	emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
   140  	emit.Int(w.BinWriter, 5)
   141  	emit.Opcodes(w.BinWriter, opcode.PACK)
   142  	emit.String(w.BinWriter, "LastPaymentNEP11")
   143  	emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
   144  	emit.Opcodes(w.BinWriter, opcode.RET)
   145  	update3Off := w.Len()
   146  	emit.Int(w.BinWriter, 3)
   147  	emit.Opcodes(w.BinWriter, opcode.JMP, 2+1)
   148  	updateOff := w.Len()
   149  	emit.Int(w.BinWriter, 2)
   150  	emit.Opcodes(w.BinWriter, opcode.PACK)
   151  	emit.AppCallNoArgs(w.BinWriter, mgmtHash, "update", callflag.All)
   152  	emit.Opcodes(w.BinWriter, opcode.DROP)
   153  	emit.Opcodes(w.BinWriter, opcode.RET)
   154  	destroyOff := w.Len()
   155  	emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All)
   156  	emit.Opcodes(w.BinWriter, opcode.DROP)
   157  	emit.Opcodes(w.BinWriter, opcode.RET)
   158  	invalidStack1Off := w.Len()
   159  	emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array
   160  	emit.Opcodes(w.BinWriter, opcode.RET)
   161  	invalidStack2Off := w.Len()
   162  	emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item
   163  	emit.Opcodes(w.BinWriter, opcode.RET)
   164  	callT0Off := w.Len()
   165  	emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET)
   166  	callT1Off := w.Len()
   167  	emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET)
   168  	callT2Off := w.Len()
   169  	emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET)
   170  	burnGasOff := w.Len()
   171  	emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas)
   172  	emit.Opcodes(w.BinWriter, opcode.RET)
   173  	invocCounterOff := w.Len()
   174  	emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetInvocationCounter)
   175  	emit.Opcodes(w.BinWriter, opcode.RET)
   176  
   177  	script := w.Bytes()
   178  	m := manifest.NewManifest("TestMain")
   179  	m.ABI.Methods = []manifest.Method{
   180  		{
   181  			Name:   "add",
   182  			Offset: addOff,
   183  			Parameters: []manifest.Parameter{
   184  				manifest.NewParameter("addend1", smartcontract.IntegerType),
   185  				manifest.NewParameter("addend2", smartcontract.IntegerType),
   186  			},
   187  			ReturnType: smartcontract.IntegerType,
   188  		},
   189  		{
   190  			Name:   "add",
   191  			Offset: addMultiOff,
   192  			Parameters: []manifest.Parameter{
   193  				manifest.NewParameter("addend1", smartcontract.IntegerType),
   194  				manifest.NewParameter("addend2", smartcontract.IntegerType),
   195  				manifest.NewParameter("addend3", smartcontract.IntegerType),
   196  			},
   197  			ReturnType: smartcontract.IntegerType,
   198  		},
   199  		{
   200  			Name:       "ret7",
   201  			Offset:     ret7Off,
   202  			Parameters: []manifest.Parameter{},
   203  			ReturnType: smartcontract.IntegerType,
   204  		},
   205  		{
   206  			Name:       "drop",
   207  			Offset:     dropOff,
   208  			ReturnType: smartcontract.VoidType,
   209  		},
   210  		{
   211  			Name:       manifest.MethodInit,
   212  			Offset:     initOff,
   213  			ReturnType: smartcontract.VoidType,
   214  		},
   215  		{
   216  			Name:   "add3",
   217  			Offset: add3Off,
   218  			Parameters: []manifest.Parameter{
   219  				manifest.NewParameter("addend", smartcontract.IntegerType),
   220  			},
   221  			ReturnType: smartcontract.IntegerType,
   222  		},
   223  		{
   224  			Name:       "invalidReturn",
   225  			Offset:     invalidRetOff,
   226  			ReturnType: smartcontract.IntegerType,
   227  		},
   228  		{
   229  			Name:       "justReturn",
   230  			Offset:     justRetOff,
   231  			ReturnType: smartcontract.VoidType,
   232  		},
   233  		{
   234  			Name:       manifest.MethodVerify,
   235  			Offset:     verifyOff,
   236  			ReturnType: smartcontract.BoolType,
   237  		},
   238  		{
   239  			Name:   manifest.MethodDeploy,
   240  			Offset: deployOff,
   241  			Parameters: []manifest.Parameter{
   242  				manifest.NewParameter("data", smartcontract.AnyType),
   243  				manifest.NewParameter("isUpdate", smartcontract.BoolType),
   244  			},
   245  			ReturnType: smartcontract.VoidType,
   246  		},
   247  		{
   248  			Name:       "getValue",
   249  			Offset:     getValOff,
   250  			ReturnType: smartcontract.StringType,
   251  		},
   252  		{
   253  			Name:   "putValue",
   254  			Offset: putValOff,
   255  			Parameters: []manifest.Parameter{
   256  				manifest.NewParameter("value", smartcontract.StringType),
   257  			},
   258  			ReturnType: smartcontract.VoidType,
   259  		},
   260  		{
   261  			Name:   "delValue",
   262  			Offset: delValOff,
   263  			Parameters: []manifest.Parameter{
   264  				manifest.NewParameter("key", smartcontract.StringType),
   265  			},
   266  			ReturnType: smartcontract.VoidType,
   267  		},
   268  		{
   269  			Name:   manifest.MethodOnNEP11Payment,
   270  			Offset: onNEP11PaymentOff,
   271  			Parameters: []manifest.Parameter{
   272  				manifest.NewParameter("from", smartcontract.Hash160Type),
   273  				manifest.NewParameter("amount", smartcontract.IntegerType),
   274  				manifest.NewParameter("tokenid", smartcontract.ByteArrayType),
   275  				manifest.NewParameter("data", smartcontract.AnyType),
   276  			},
   277  			ReturnType: smartcontract.VoidType,
   278  		},
   279  		{
   280  			Name:   manifest.MethodOnNEP17Payment,
   281  			Offset: onNEP17PaymentOff,
   282  			Parameters: []manifest.Parameter{
   283  				manifest.NewParameter("from", smartcontract.Hash160Type),
   284  				manifest.NewParameter("amount", smartcontract.IntegerType),
   285  				manifest.NewParameter("data", smartcontract.AnyType),
   286  			},
   287  			ReturnType: smartcontract.VoidType,
   288  		},
   289  		{
   290  			Name:   "update",
   291  			Offset: updateOff,
   292  			Parameters: []manifest.Parameter{
   293  				manifest.NewParameter("nef", smartcontract.ByteArrayType),
   294  				manifest.NewParameter("manifest", smartcontract.ByteArrayType),
   295  			},
   296  			ReturnType: smartcontract.VoidType,
   297  		},
   298  		{
   299  			Name:   "update",
   300  			Offset: update3Off,
   301  			Parameters: []manifest.Parameter{
   302  				manifest.NewParameter("nef", smartcontract.ByteArrayType),
   303  				manifest.NewParameter("manifest", smartcontract.ByteArrayType),
   304  				manifest.NewParameter("data", smartcontract.AnyType),
   305  			},
   306  			ReturnType: smartcontract.VoidType,
   307  		},
   308  		{
   309  			Name:       "destroy",
   310  			Offset:     destroyOff,
   311  			ReturnType: smartcontract.VoidType,
   312  		},
   313  		{
   314  			Name:       "invalidStack1",
   315  			Offset:     invalidStack1Off,
   316  			ReturnType: smartcontract.AnyType,
   317  		},
   318  		{
   319  			Name:       "invalidStack2",
   320  			Offset:     invalidStack2Off,
   321  			ReturnType: smartcontract.AnyType,
   322  		},
   323  		{
   324  			Name:   "callT0",
   325  			Offset: callT0Off,
   326  			Parameters: []manifest.Parameter{
   327  				manifest.NewParameter("address", smartcontract.Hash160Type),
   328  			},
   329  			ReturnType: smartcontract.IntegerType,
   330  		},
   331  		{
   332  			Name:       "callT1",
   333  			Offset:     callT1Off,
   334  			ReturnType: smartcontract.IntegerType,
   335  		},
   336  		{
   337  			Name:       "callT2",
   338  			Offset:     callT2Off,
   339  			ReturnType: smartcontract.IntegerType,
   340  		},
   341  		{
   342  			Name:   "burnGas",
   343  			Offset: burnGasOff,
   344  			Parameters: []manifest.Parameter{
   345  				manifest.NewParameter("amount", smartcontract.IntegerType),
   346  			},
   347  			ReturnType: smartcontract.VoidType,
   348  		},
   349  		{
   350  			Name:       "invocCounter",
   351  			Offset:     invocCounterOff,
   352  			ReturnType: smartcontract.IntegerType,
   353  		},
   354  	}
   355  	m.ABI.Events = []manifest.Event{
   356  		{
   357  			Name: "LastPaymentNEP17",
   358  			Parameters: []manifest.Parameter{
   359  				{
   360  					Name: "from",
   361  					Type: smartcontract.Hash160Type,
   362  				},
   363  				{
   364  					Name: "to",
   365  					Type: smartcontract.Hash160Type,
   366  				},
   367  				{
   368  					Name: "amount",
   369  					Type: smartcontract.IntegerType,
   370  				},
   371  				{
   372  					Name: "data",
   373  					Type: smartcontract.AnyType,
   374  				},
   375  			},
   376  		},
   377  		{
   378  			Name: "LastPaymentNEP11",
   379  			Parameters: []manifest.Parameter{
   380  				{
   381  					Name: "from",
   382  					Type: smartcontract.Hash160Type,
   383  				},
   384  				{
   385  					Name: "to",
   386  					Type: smartcontract.Hash160Type,
   387  				},
   388  				{
   389  					Name: "amount",
   390  					Type: smartcontract.IntegerType,
   391  				},
   392  				{
   393  					Name: "tokenId",
   394  					Type: smartcontract.ByteArrayType,
   395  				},
   396  				{
   397  					Name: "data",
   398  					Type: smartcontract.AnyType,
   399  				},
   400  			},
   401  		},
   402  		{
   403  			Name: "event", // This event is not emitted by the contract code and needed for System.Runtime.Notify tests.
   404  			Parameters: []manifest.Parameter{
   405  				{
   406  					Name: "any",
   407  					Type: smartcontract.AnyType,
   408  				},
   409  			},
   410  		},
   411  	}
   412  	m.Permissions = make([]manifest.Permission, 2)
   413  	m.Permissions[0].Contract.Type = manifest.PermissionHash
   414  	m.Permissions[0].Contract.Value = neoHash
   415  	m.Permissions[0].Methods.Add("balanceOf")
   416  
   417  	m.Permissions[1].Contract.Type = manifest.PermissionHash
   418  	m.Permissions[1].Contract.Value = util.Uint160{}
   419  	m.Permissions[1].Methods.Add("method")
   420  
   421  	// Generate NEF file.
   422  	ne, err := nef.NewFile(script)
   423  	require.NoError(t, err)
   424  	ne.Tokens = []nef.MethodToken{
   425  		{
   426  			Hash:       neoHash,
   427  			Method:     "balanceOf",
   428  			ParamCount: 1,
   429  			HasReturn:  true,
   430  			CallFlag:   callflag.ReadStates,
   431  		},
   432  		{
   433  			Hash:      util.Uint160{},
   434  			Method:    "method",
   435  			HasReturn: true,
   436  			CallFlag:  callflag.ReadStates,
   437  		},
   438  	}
   439  	ne.Checksum = ne.CalculateChecksum()
   440  
   441  	// Write first NEF file.
   442  	bytes, err := ne.Bytes()
   443  	require.NoError(t, err)
   444  	if saveState {
   445  		err = os.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm)
   446  		require.NoError(t, err)
   447  	}
   448  
   449  	// Write first manifest file.
   450  	mData, err := json.Marshal(m)
   451  	require.NoError(t, err)
   452  	if saveState {
   453  		err = os.WriteFile(helper1ContractManifestPath, mData, os.ModePerm)
   454  		require.NoError(t, err)
   455  	}
   456  
   457  	// Create hash of the first contract assuming that sender is single-chain validator.
   458  	h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name)
   459  
   460  	currScript := []byte{byte(opcode.RET)}
   461  	m = manifest.NewManifest("TestAux")
   462  	m.ABI.Methods = []manifest.Method{
   463  		{
   464  			Name:       "simpleMethod",
   465  			Offset:     0,
   466  			ReturnType: smartcontract.VoidType,
   467  		},
   468  	}
   469  	perm := manifest.NewPermission(manifest.PermissionHash, h)
   470  	perm.Methods.Add("add")
   471  	perm.Methods.Add("drop")
   472  	perm.Methods.Add("add3")
   473  	perm.Methods.Add("invalidReturn")
   474  	perm.Methods.Add("justReturn")
   475  	perm.Methods.Add("getValue")
   476  	m.Permissions = append(m.Permissions, *perm)
   477  	ne, err = nef.NewFile(currScript)
   478  	require.NoError(t, err)
   479  
   480  	// Write second NEF file.
   481  	bytes, err = ne.Bytes()
   482  	require.NoError(t, err)
   483  	if saveState {
   484  		err = os.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm)
   485  		require.NoError(t, err)
   486  	}
   487  
   488  	// Write second manifest file.
   489  	mData, err = json.Marshal(m)
   490  	require.NoError(t, err)
   491  	if saveState {
   492  		err = os.WriteFile(helper2ContractManifestPath, mData, os.ModePerm)
   493  		require.NoError(t, err)
   494  	}
   495  }