package contract_test import ( "math/big" "path/filepath" "testing" "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", "contracts") func TestGetCallFlags(t *testing.T) { bc, _ := chain.NewSingle(t) ic := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) ic.VM.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, callflag.All) require.NoError(t, contract.GetCallFlags(ic)) require.Equal(t, int64(callflag.All), ic.VM.Estack().Pop().Value().(*big.Int).Int64()) } func TestCall(t *testing.T) { bc, _ := chain.NewSingle(t) ic := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) cs, currCs := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, native.PutContractState(ic.DAO, cs)) require.NoError(t, native.PutContractState(ic.DAO, currCs)) currScript := currCs.NEF.Script h := cs.Hash addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)}) t.Run("Good", func(t *testing.T) { t.Run("2 arguments", func(t *testing.T) { loadScript(ic, currScript, 42) ic.VM.Estack().PushVal(addArgs) ic.VM.Estack().PushVal(callflag.All) ic.VM.Estack().PushVal("add") ic.VM.Estack().PushVal(h.BytesBE()) require.NoError(t, contract.Call(ic)) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, big.NewInt(3), ic.VM.Estack().Pop().Value()) require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value()) }) t.Run("3 arguments", func(t *testing.T) { loadScript(ic, currScript, 42) ic.VM.Estack().PushVal(stackitem.NewArray( append(addArgs.Value().([]stackitem.Item), stackitem.Make(3)))) ic.VM.Estack().PushVal(callflag.All) ic.VM.Estack().PushVal("add") ic.VM.Estack().PushVal(h.BytesBE()) require.NoError(t, contract.Call(ic)) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, big.NewInt(6), ic.VM.Estack().Pop().Value()) require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value()) }) }) t.Run("CallExInvalidFlag", func(t *testing.T) { loadScript(ic, currScript, 42) ic.VM.Estack().PushVal(addArgs) ic.VM.Estack().PushVal(byte(0xFF)) ic.VM.Estack().PushVal("add") ic.VM.Estack().PushVal(h.BytesBE()) require.Error(t, contract.Call(ic)) }) runInvalid := func(args ...interface{}) func(t *testing.T) { return func(t *testing.T) { loadScriptWithHashAndFlags(ic, currScript, h, callflag.All, 42) for i := range args { ic.VM.Estack().PushVal(args[i]) } // interops can both return error and panic, // we don't care which kind of error has occurred require.Panics(t, func() { err := contract.Call(ic) if err != nil { panic(err) } }) } } t.Run("Invalid", func(t *testing.T) { t.Run("Hash", runInvalid(addArgs, "add", h.BytesBE()[1:])) t.Run("MissingHash", runInvalid(addArgs, "add", util.Uint160{}.BytesBE())) t.Run("Method", runInvalid(addArgs, stackitem.NewInterop("add"), h.BytesBE())) t.Run("MissingMethod", runInvalid(addArgs, "sub", h.BytesBE())) t.Run("DisallowedMethod", runInvalid(stackitem.NewArray(nil), "ret7", h.BytesBE())) t.Run("Arguments", runInvalid(1, "add", h.BytesBE())) t.Run("NotEnoughArguments", runInvalid( stackitem.NewArray([]stackitem.Item{stackitem.Make(1)}), "add", h.BytesBE())) t.Run("TooMuchArguments", runInvalid( stackitem.NewArray([]stackitem.Item{ stackitem.Make(1), stackitem.Make(2), stackitem.Make(3), stackitem.Make(4)}), "add", h.BytesBE())) }) t.Run("ReturnValues", func(t *testing.T) { t.Run("Many", func(t *testing.T) { loadScript(ic, currScript, 42) ic.VM.Estack().PushVal(stackitem.NewArray(nil)) ic.VM.Estack().PushVal(callflag.All) ic.VM.Estack().PushVal("invalidReturn") ic.VM.Estack().PushVal(h.BytesBE()) require.NoError(t, contract.Call(ic)) require.Error(t, ic.VM.Run()) }) t.Run("Void", func(t *testing.T) { loadScript(ic, currScript, 42) ic.VM.Estack().PushVal(stackitem.NewArray(nil)) ic.VM.Estack().PushVal(callflag.All) ic.VM.Estack().PushVal("justReturn") ic.VM.Estack().PushVal(h.BytesBE()) require.NoError(t, contract.Call(ic)) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, stackitem.Null{}, ic.VM.Estack().Pop().Item()) require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value()) }) }) t.Run("IsolatedStack", func(t *testing.T) { loadScript(ic, currScript, 42) ic.VM.Estack().PushVal(stackitem.NewArray(nil)) ic.VM.Estack().PushVal(callflag.All) ic.VM.Estack().PushVal("drop") ic.VM.Estack().PushVal(h.BytesBE()) require.NoError(t, contract.Call(ic)) require.Error(t, ic.VM.Run()) }) t.Run("CallInitialize", func(t *testing.T) { t.Run("Directly", runInvalid(stackitem.NewArray([]stackitem.Item{}), "_initialize", h.BytesBE())) loadScript(ic, currScript, 42) ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)})) ic.VM.Estack().PushVal(callflag.All) ic.VM.Estack().PushVal("add3") ic.VM.Estack().PushVal(h.BytesBE()) require.NoError(t, contract.Call(ic)) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, big.NewInt(8), ic.VM.Estack().Pop().Value()) require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value()) }) } func loadScript(ic *interop.Context, script []byte, args ...interface{}) { ic.SpawnVM() ic.VM.LoadScriptWithFlags(script, callflag.AllowCall) for i := range args { ic.VM.Estack().PushVal(args[i]) } ic.VM.GasLimit = -1 } func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Uint160, f callflag.CallFlag, args ...interface{}) { ic.SpawnVM() ic.VM.LoadScriptWithHash(script, hash, f) for i := range args { ic.VM.Estack().PushVal(args[i]) } ic.VM.GasLimit = -1 }