github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/invocation_test.go (about)

     1  package native_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/nspcc-dev/neo-go/internal/contracts"
    11  	"github.com/nspcc-dev/neo-go/pkg/config"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/fee"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    15  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    16  	"github.com/nspcc-dev/neo-go/pkg/neotest"
    17  	"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
    18  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    19  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
    20  	"github.com/nspcc-dev/neo-go/pkg/util"
    21  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    22  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  var pathToInternalContracts = filepath.Join("..", "..", "..", "internal", "contracts")
    27  
    28  func TestNativeContract_Invoke(t *testing.T) {
    29  	const (
    30  		transferCPUFee          = 1 << 17
    31  		transferStorageFee      = 50
    32  		systemContractCallPrice = 1 << 15
    33  	)
    34  	bc, validator, committee := chain.NewMulti(t)
    35  	e := neotest.NewExecutor(t, bc, validator, committee)
    36  	gasHash := e.NativeHash(t, nativenames.Gas)
    37  
    38  	baseExecFee := bc.GetBaseExecFee()
    39  	price := fee.Opcode(baseExecFee, opcode.SYSCALL, // System.Contract.Call
    40  		opcode.PUSHDATA1, // contract hash (20 byte)
    41  		opcode.PUSHDATA1, // method
    42  		opcode.PUSH15,    // call flags
    43  		// `transfer` args:
    44  		opcode.PUSHDATA1, // from
    45  		opcode.PUSHDATA1, // to
    46  		opcode.PUSH1,     // amount
    47  		opcode.PUSHNULL,  // data
    48  		// end args
    49  		opcode.PUSH4, // amount of args
    50  		opcode.PACK,  // pack args
    51  	)
    52  	price += systemContractCallPrice*baseExecFee + // System.Contract.Call price
    53  		transferCPUFee*baseExecFee + // `transfer` itself
    54  		transferStorageFee*bc.GetStoragePrice() // `transfer` storage price
    55  
    56  	tx := e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil)
    57  	e.SignTx(t, tx, -1, validator)
    58  	e.AddNewBlock(t, tx)
    59  	e.CheckHalt(t, tx.Hash(), stackitem.Make(true))
    60  
    61  	// Enough for Call and other opcodes, but not enough for "transfer" call.
    62  	tx = e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil)
    63  	e.SignTx(t, tx, price-1, validator)
    64  	e.AddNewBlock(t, tx)
    65  	e.CheckFault(t, tx.Hash(), "gas limit exceeded")
    66  }
    67  
    68  func TestNativeContract_InvokeInternal(t *testing.T) {
    69  	bc, validator, committee := chain.NewMulti(t)
    70  	e := neotest.NewExecutor(t, bc, validator, committee)
    71  	clState := bc.GetContractState(e.NativeHash(t, nativenames.CryptoLib))
    72  	require.NotNil(t, clState)
    73  	md := clState.Manifest.ABI.GetMethod("ripemd160", 1)
    74  	require.NotNil(t, md)
    75  
    76  	t.Run("fail, bad current script hash", func(t *testing.T) {
    77  		ic, err := bc.GetTestVM(trigger.Application, nil, nil)
    78  		require.NoError(t, err)
    79  		v := ic.SpawnVM()
    80  		fakeH := util.Uint160{1, 2, 3}
    81  		v.LoadScriptWithHash(clState.NEF.Script, fakeH, callflag.All)
    82  		input := []byte{1, 2, 3, 4}
    83  		v.Estack().PushVal(input)
    84  		v.Context().Jump(md.Offset)
    85  
    86  		// Bad current script hash
    87  		err = v.Run()
    88  		require.Error(t, err)
    89  		require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s (version 0) not found", fakeH.StringLE())), err.Error())
    90  	})
    91  
    92  	/*
    93  		t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) {
    94  			bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
    95  				c.NativeUpdateHistories = map[string][]uint32{
    96  					nativenames.Policy:      {0},
    97  					nativenames.Neo:         {0},
    98  					nativenames.Gas:         {0},
    99  					nativenames.Designation: {0},
   100  					nativenames.StdLib:      {0},
   101  					nativenames.Management:  {0},
   102  					nativenames.Oracle:      {0},
   103  					nativenames.Ledger:      {0},
   104  					nativenames.CryptoLib:   {1},
   105  				}
   106  			})
   107  			eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad)
   108  
   109  			ic, err := bcBad.GetTestVM(trigger.Application, nil, nil)
   110  			require.NoError(t, err)
   111  			v := ic.SpawnVM()
   112  			v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
   113  			input := []byte{1, 2, 3, 4}
   114  			v.Estack().PushVal(input)
   115  			v.Context().Jump(md.Offset)
   116  
   117  			// It's prohibited to call natives before NativeUpdateHistory[0] height.
   118  			err = v.Run()
   119  			require.Error(t, err)
   120  			require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1"))
   121  
   122  			// Add new block => CryptoLib should be active now.
   123  			eBad.AddNewBlock(t)
   124  			ic, err = bcBad.GetTestVM(trigger.Application, nil, nil)
   125  			require.NoError(t, err)
   126  			v = ic.SpawnVM()
   127  			v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
   128  			v.Estack().PushVal(input)
   129  			v.Context().Jump(md.Offset)
   130  
   131  			require.NoError(t, v.Run())
   132  			value := v.Estack().Pop().Bytes()
   133  			require.Equal(t, hash.RipeMD160(input).BytesBE(), value)
   134  		})
   135  	*/
   136  
   137  	manState := bc.GetContractState(e.NativeHash(t, nativenames.Management))
   138  	require.NotNil(t, manState)
   139  	mdDeploy := manState.Manifest.ABI.GetMethod("deploy", 2)
   140  	require.NotNil(t, mdDeploy)
   141  	t.Run("fail, bad call flag", func(t *testing.T) {
   142  		ic, err := bc.GetTestVM(trigger.Application, nil, nil)
   143  		require.NoError(t, err)
   144  		v := ic.SpawnVM()
   145  		v.LoadScriptWithHash(manState.NEF.Script, manState.Hash, callflag.States|callflag.AllowNotify)
   146  		input := []byte{1, 2, 3, 4}
   147  		v.Estack().PushVal(input)
   148  		v.Estack().PushVal(input)
   149  		v.Context().Jump(mdDeploy.Offset)
   150  
   151  		// Can't call with these flags, Aspidochelone is active.
   152  		err = v.Run()
   153  		require.Error(t, err)
   154  		require.True(t, strings.Contains(err.Error(), "missing call flags for native 0 `deploy` operation call"))
   155  	})
   156  
   157  	t.Run("good, pre-aspidochelone deploy", func(t *testing.T) {
   158  		bc, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
   159  			c.Hardforks = map[string]uint32{
   160  				config.HFAspidochelone.String(): 100500,
   161  			}
   162  		})
   163  
   164  		ic, err := bc.GetTestVM(trigger.Application, nil, nil)
   165  		require.NoError(t, err)
   166  		v := ic.SpawnVM()
   167  		v.LoadScriptWithHash(manState.NEF.Script, manState.Hash, callflag.States|callflag.AllowNotify)
   168  		input := []byte{1, 2, 3, 4}
   169  		v.Estack().PushVal(input)
   170  		v.Estack().PushVal(input)
   171  		v.Context().Jump(mdDeploy.Offset)
   172  
   173  		// We have an invalid input, but call flags are OK.
   174  		err = v.Run()
   175  		require.Error(t, err)
   176  		require.True(t, strings.Contains(err.Error(), "invalid NEF file"))
   177  	})
   178  
   179  	t.Run("success", func(t *testing.T) {
   180  		ic, err := bc.GetTestVM(trigger.Application, nil, nil)
   181  		require.NoError(t, err)
   182  		v := ic.SpawnVM()
   183  		v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All)
   184  		input := []byte{1, 2, 3, 4}
   185  		v.Estack().PushVal(input)
   186  		v.Context().Jump(md.Offset)
   187  
   188  		require.NoError(t, v.Run())
   189  
   190  		value := v.Estack().Pop().Bytes()
   191  		require.Equal(t, hash.RipeMD160(input).BytesBE(), value)
   192  	})
   193  }
   194  
   195  func TestNativeContract_InvokeOtherContract(t *testing.T) {
   196  	bc, validator, committee := chain.NewMulti(t)
   197  	e := neotest.NewExecutor(t, bc, validator, committee)
   198  	managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
   199  	gasInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
   200  
   201  	cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, validator.ScriptHash())
   202  	cs.Hash = state.CreateContractHash(validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash
   203  	manifB, err := json.Marshal(cs.Manifest)
   204  	require.NoError(t, err)
   205  	nefB, err := cs.NEF.Bytes()
   206  	require.NoError(t, err)
   207  	si, err := cs.ToStackItem()
   208  	require.NoError(t, err)
   209  	managementInvoker.Invoke(t, si, "deploy", nefB, manifB)
   210  
   211  	t.Run("non-native, no return", func(t *testing.T) {
   212  		// `onNEP17Payment` will be invoked on test contract from GAS contract.
   213  		gasInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), cs.Hash, 1, nil)
   214  	})
   215  }