From ec900c7ff76bfa5b0c0049e8cc07b256f53375a4 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 7 May 2020 14:11:59 +0300 Subject: [PATCH] core: implement System.Contract.Call interop --- pkg/core/interop/context.go | 10 ++++++++++ pkg/core/interop_system.go | 34 ++++++++++++++++++++++++++++++++ pkg/core/interops.go | 17 +++------------- pkg/core/native_contract_test.go | 4 ++++ 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index ff42e8fa4..59b2337b0 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -84,6 +84,16 @@ type ContractMD struct { Methods map[string]MethodAndPrice } +// GetContract returns script of the contract with the specified hash. +func (ic *Context) GetContract(h util.Uint160) ([]byte, bool) { + cs, err := ic.DAO.GetContractState(h) + if err != nil { + return nil, false + } + hasDynamicInvoke := (cs.Properties & smartcontract.HasDynamicInvoke) != 0 + return cs.Script, hasDynamicInvoke +} + // NewContractMD returns Contract with the specified list of methods. func NewContractMD(name string) *ContractMD { c := &ContractMD{ diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 30f085f7c..4f6bfea4b 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" "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" @@ -470,6 +471,39 @@ func storageContextAsReadOnly(ic *interop.Context, v *vm.VM) error { return nil } +// contractCall calls a contract. +func contractCall(ic *interop.Context, v *vm.VM) error { + h := v.Estack().Pop().Bytes() + method := v.Estack().Pop().Item() + args := v.Estack().Pop().Item() + return contractCallExInternal(ic, v, h, method, args, smartcontract.All) +} + +// contractCallEx calls a contract with flags. +func contractCallEx(ic *interop.Context, v *vm.VM) error { + h := v.Estack().Pop().Bytes() + method := v.Estack().Pop().Item() + args := v.Estack().Pop().Item() + flags := smartcontract.CallFlag(int32(v.Estack().Pop().BigInt().Int64())) + return contractCallExInternal(ic, v, h, method, args, flags) +} + +func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method vm.StackItem, args vm.StackItem, _ smartcontract.CallFlag) error { + u, err := util.Uint160DecodeBytesBE(h) + if err != nil { + return errors.New("invalid contract hash") + } + script, _ := ic.GetContract(u) + if script == nil { + return errors.New("contract not found") + } + // TODO perform flags checking after #923 + v.LoadScript(script) + v.Estack().PushVal(args) + v.Estack().PushVal(method) + return nil +} + // contractDestroy destroys a contract. func contractDestroy(ic *interop.Context, v *vm.VM) error { if ic.Trigger != trigger.Application { diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 03728282a..b1be43361 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -16,8 +16,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/native" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) @@ -27,18 +25,7 @@ import ( func SpawnVM(ic *interop.Context) *vm.VM { vm := vm.New() bc := ic.Chain.(*Blockchain) - vm.SetScriptGetter(func(hash util.Uint160) ([]byte, bool) { - if c := bc.contracts.ByHash(hash); c != nil { - meta := c.Metadata() - return meta.Script, (meta.Manifest.Features&smartcontract.HasDynamicInvoke != 0) - } - cs, err := ic.DAO.GetContractState(hash) - if err != nil { - return nil, false - } - hasDynamicInvoke := (cs.Properties & smartcontract.HasDynamicInvoke) != 0 - return cs.Script, hasDynamicInvoke - }) + vm.SetScriptGetter(ic.GetContract) vm.RegisterInteropGetter(getSystemInterop(ic)) vm.RegisterInteropGetter(getNeoInterop(ic)) vm.RegisterInteropGetter(bc.contracts.GetNativeInterop(ic)) @@ -84,6 +71,8 @@ var systemInterops = []interop.Function{ {Name: "System.Blockchain.GetHeight", Func: bcGetHeight, Price: 1}, {Name: "System.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 200}, {Name: "System.Blockchain.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100}, + {Name: "System.Contract.Call", Func: contractCall, Price: 1}, + {Name: "System.Contract.CallEx", Func: contractCallEx, Price: 1}, {Name: "System.Contract.Destroy", Func: contractDestroy, Price: 1}, {Name: "System.Contract.GetStorageContext", Func: contractGetStorageContext, Price: 1}, {Name: "System.ExecutionEngine.GetCallingScriptHash", Func: engineGetCallingScriptHash, Price: 1}, diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 90e422483..fd2d10024 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -85,6 +86,9 @@ func TestNativeContract_Invoke(t *testing.T) { tn := newTestNative() chain.registerNative(tn) + err := chain.dao.PutContractState(&state.Contract{Script: tn.meta.Script}) + require.NoError(t, err) + w := io.NewBufBinWriter() emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "sum", int64(14), int64(28)) script := w.Bytes()