From 4bf88ba6b00e3f6d04fe10860ded576a3ff2f69f Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 19:03:05 +0300 Subject: [PATCH] core: decouple native contracts from interop service Closes #1191. --- pkg/core/helper_test.go | 2 + pkg/core/interop/context.go | 22 +++++------ pkg/core/interops.go | 4 +- pkg/core/native/contract.go | 51 ------------------------- pkg/core/native/interop.go | 36 ++++++++++++++++- pkg/core/native/native_gas.go | 5 +-- pkg/core/native/native_neo.go | 7 ++-- pkg/core/native/native_nep5.go | 3 +- pkg/core/native/policy.go | 6 +-- pkg/core/native_contract_test.go | 47 +++++++++++++++++++++++ pkg/rpc/client/nep5.go | 4 +- pkg/rpc/client/policy.go | 4 +- pkg/rpc/server/server_test.go | 2 +- pkg/rpc/server/testdata/testblocks.acc | Bin 7333 -> 7333 bytes 14 files changed, 110 insertions(+), 83 deletions(-) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index a58d76b1a..912b85360 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -177,8 +177,10 @@ func TestCreateBasicChain(t *testing.T) { gasHash := bc.contracts.GAS.Hash neoHash := bc.contracts.NEO.Hash + policyHash := bc.contracts.Policy.Hash t.Logf("native GAS hash: %v", gasHash) t.Logf("native NEO hash: %v", neoHash) + t.Logf("native Policy hash: %v", policyHash) priv0 := testchain.PrivateKeyByID(0) priv0ScriptHash := priv0.GetScriptHash() diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 775e21f1b..559c92291 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -82,25 +82,25 @@ type Contract interface { // ContractMD represents native contract instance. type ContractMD struct { - Manifest manifest.Manifest - ServiceName string - ServiceID uint32 - ContractID int32 - Script []byte - Hash util.Uint160 - Methods map[string]MethodAndPrice + Manifest manifest.Manifest + Name string + ContractID int32 + Script []byte + Hash util.Uint160 + Methods map[string]MethodAndPrice } // NewContractMD returns Contract with the specified list of methods. func NewContractMD(name string) *ContractMD { c := &ContractMD{ - ServiceName: name, - ServiceID: emit.InteropNameToID([]byte(name)), - Methods: make(map[string]MethodAndPrice), + Name: name, + Methods: make(map[string]MethodAndPrice), } w := io.NewBufBinWriter() - emit.Syscall(w.BinWriter, c.ServiceName) + emit.String(w.BinWriter, c.Name) + emit.Syscall(w.BinWriter, "Neo.Native.Call") + c.Script = w.Bytes() c.Hash = hash.Hash160(c.Script) c.Manifest = *manifest.DefaultManifest(c.Hash) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 86b42713e..2ee8d780f 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -28,9 +28,6 @@ func SpawnVM(ic *interop.Context) *vm.VM { vm := vm.NewWithTrigger(ic.Trigger) vm.RegisterInteropGetter(getSystemInterop(ic)) vm.RegisterInteropGetter(getNeoInterop(ic)) - if ic.Chain != nil { - vm.RegisterInteropGetter(ic.Chain.(*Blockchain).contracts.GetNativeInterop(ic)) - } ic.ScriptGetter = vm return vm } @@ -129,6 +126,7 @@ var neoInterops = []interop.Function{ {Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256k1", Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0}, {Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1000000}, {Name: "Neo.Crypto.RIPEMD160", Func: crypto.RipeMD160, Price: 1000000}, + {Name: "Neo.Native.Call", Func: native.Call, Price: 0}, {Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0, RequiredFlags: smartcontract.AllowModifyStates}, } diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 8dee4230a..a77c9a963 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -1,15 +1,11 @@ package native import ( - "fmt" - "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/io" "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" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" - "github.com/pkg/errors" ) // Contracts is a set of registered native contracts. @@ -32,16 +28,6 @@ func (cs *Contracts) ByHash(h util.Uint160) interop.Contract { return nil } -// ByID returns native contract with the specified id. -func (cs *Contracts) ByID(id uint32) interop.Contract { - for _, ctr := range cs.Contracts { - if ctr.Metadata().ServiceID == id { - return ctr - } - } - return nil -} - // NewContracts returns new set of native contracts with new GAS, NEO and Policy // contracts. func NewContracts() *Contracts { @@ -79,40 +65,3 @@ func (cs *Contracts) GetPersistScript() []byte { cs.persistScript = w.Bytes() return cs.persistScript } - -// GetNativeInterop returns an interop getter for a given set of contracts. -func (cs *Contracts) GetNativeInterop(ic *interop.Context) func(uint32) *vm.InteropFuncPrice { - return func(id uint32) *vm.InteropFuncPrice { - if c := cs.ByID(id); c != nil { - return &vm.InteropFuncPrice{ - Func: getNativeInterop(ic, c), - } - } - return nil - } -} - -// getNativeInterop returns native contract interop. -func getNativeInterop(ic *interop.Context, c interop.Contract) func(v *vm.VM) error { - return func(v *vm.VM) error { - h := v.GetCurrentScriptHash() - if !h.Equals(c.Metadata().Hash) { - return errors.New("invalid hash") - } - name := string(v.Estack().Pop().Bytes()) - args := v.Estack().Pop().Array() - m, ok := c.Metadata().Methods[name] - if !ok { - return fmt.Errorf("method %s not found", name) - } - if !v.Context().GetCallFlags().Has(m.RequiredFlags) { - return errors.New("missing call flags") - } - if !v.AddGas(m.Price) { - return errors.New("gas limit exceeded") - } - result := m.Func(ic, args) - v.Estack().PushVal(result) - return nil - } -} diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index d28e8b618..63e3b1da4 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -27,8 +27,42 @@ func Deploy(ic *interop.Context, _ *vm.VM) error { return err } if err := native.Initialize(ic); err != nil { - return fmt.Errorf("initializing %s native contract: %v", md.ServiceName, err) + return fmt.Errorf("initializing %s native contract: %v", md.Name, err) } } return nil } + +// Call calls specified native contract method. +func Call(ic *interop.Context, v *vm.VM) error { + name := string(v.Estack().Pop().Bytes()) + var c interop.Contract + for _, ctr := range ic.Natives { + if ctr.Metadata().Name == name { + c = ctr + break + } + } + if c == nil { + return fmt.Errorf("native contract %s not found", name) + } + h := v.GetCurrentScriptHash() + if !h.Equals(c.Metadata().Hash) { + return errors.New("it is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used") + } + operation := string(v.Estack().Pop().Bytes()) + args := v.Estack().Pop().Array() + m, ok := c.Metadata().Methods[operation] + if !ok { + return fmt.Errorf("method %s not found", operation) + } + if !v.Context().GetCallFlags().Has(m.RequiredFlags) { + return errors.New("missing call flags") + } + if !v.AddGas(m.Price) { + return errors.New("gas limit exceeded") + } + result := m.Func(ic, args) + v.Estack().PushVal(result) + return nil +} diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index d7bc10b5e..1cb0ad5eb 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -19,7 +19,7 @@ type GAS struct { NEO *NEO } -const gasSyscallName = "Neo.Native.Tokens.GAS" +const gasName = "GAS" const gasContractID = -2 // GASFactor is a divisor for finding GAS integral value. @@ -29,8 +29,7 @@ const initialGAS = 30000000 // NewGAS returns GAS native contract. func NewGAS() *GAS { g := &GAS{} - nep5 := newNEP5Native(gasSyscallName) - nep5.name = "GAS" + nep5 := newNEP5Native(gasName) nep5.symbol = "gas" nep5.decimals = 8 nep5.factor = GASFactor diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index c54614569..731e4e4f7 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -38,8 +38,8 @@ type keyWithVotes struct { } const ( - neoSyscallName = "Neo.Native.Tokens.NEO" - neoContractID = -1 + neoName = "NEO" + neoContractID = -1 // NEOTotalSupply is the total amount of NEO in the system. NEOTotalSupply = 100000000 // prefixValidator is a prefix used to store validator's data. @@ -68,8 +68,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte { // NewNEO returns NEO native contract. func NewNEO() *NEO { n := &NEO{} - nep5 := newNEP5Native(neoSyscallName) - nep5.name = "NEO" + nep5 := newNEP5Native(neoName) nep5.symbol = "neo" nep5.decimals = 0 nep5.factor = 1 diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index f7dc87aed..af743023c 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -29,7 +29,6 @@ func makeAccountKey(h util.Uint160) []byte { // nep5TokenNative represents NEP-5 token contract. type nep5TokenNative struct { interop.ContractMD - name string symbol string decimals int64 factor int64 @@ -92,7 +91,7 @@ func (c *nep5TokenNative) Initialize(_ *interop.Context) error { } func (c *nep5TokenNative) Name(_ *interop.Context, _ []stackitem.Item) stackitem.Item { - return stackitem.NewByteArray([]byte(c.name)) + return stackitem.NewByteArray([]byte(c.ContractMD.Name)) } func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index e80dc3264..04ff6a72a 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -19,8 +19,8 @@ import ( ) const ( - policySyscallName = "Neo.Native.Policy" - policyContractID = -3 + policyName = "Policy" + policyContractID = -3 defaultMaxBlockSize = 1024 * 256 defaultMaxTransactionsPerBlock = 512 @@ -59,7 +59,7 @@ var _ interop.Contract = (*Policy)(nil) // newPolicy returns Policy native contract. func newPolicy() *Policy { - p := &Policy{ContractMD: *interop.NewContractMD(policySyscallName)} + p := &Policy{ContractMD: *interop.NewContractMD(policyName)} p.ContractID = policyContractID p.Manifest.Features |= smartcontract.HasStorage diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index b2a9623d6..b1df21ea1 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -1,15 +1,21 @@ package core import ( + "math/big" "testing" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/storage" "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" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "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" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -140,3 +146,44 @@ func TestNativeContract_Invoke(t *testing.T) { require.Fail(t, "onPersist wasn't called") } } + +func TestNativeContract_InvokeInternal(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + + tn := newTestNative() + chain.registerNative(tn) + + err := chain.dao.PutContractState(&state.Contract{ + Script: tn.meta.Script, + Manifest: tn.meta.Manifest, + }) + require.NoError(t, err) + + v := vm.New() + v.GasLimit = -1 + ic := chain.newInteropContext(trigger.Application, + dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) + + t.Run("fail, bad current script hash", func(t *testing.T) { + v.LoadScriptWithHash([]byte{1}, util.Uint160{1, 2, 3}, smartcontract.All) + v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(14)), stackitem.NewBigInteger(big.NewInt(28))})) + v.Estack().PushVal("sum") + v.Estack().PushVal(tn.Metadata().Name) + + // it's prohibited to call natives directly + require.Error(t, native.Call(ic, v)) + }) + + t.Run("success", func(t *testing.T) { + v.LoadScriptWithHash([]byte{1}, tn.Metadata().Hash, smartcontract.All) + v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(14)), stackitem.NewBigInteger(big.NewInt(28))})) + v.Estack().PushVal("sum") + v.Estack().PushVal(tn.Metadata().Name) + + require.NoError(t, native.Call(ic, v)) + + value := v.Estack().Pop().BigInt() + require.Equal(t, int64(42), value.Int64()) + }) +} diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index 4d99b313d..150b44d4f 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -23,9 +23,9 @@ type AddrAndAmount struct { var ( // NeoContractHash is a hash of the NEO native contract. - NeoContractHash, _ = util.Uint160DecodeStringLE("9bde8f209c88dd0e7ca3bf0af0f476cdd8207789") + NeoContractHash, _ = util.Uint160DecodeStringBE("25059ecb4878d3a875f91c51ceded330d4575fde") // GasContractHash is a hash of the GAS native contract. - GasContractHash, _ = util.Uint160DecodeStringLE("8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b") + GasContractHash, _ = util.Uint160DecodeStringBE("bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e66") ) // NEP5Decimals invokes `decimals` NEP5 method on a specified contract. diff --git a/pkg/rpc/client/policy.go b/pkg/rpc/client/policy.go index e0d9b46e6..4f9b15103 100644 --- a/pkg/rpc/client/policy.go +++ b/pkg/rpc/client/policy.go @@ -9,8 +9,8 @@ import ( "github.com/pkg/errors" ) -// PolicyContractHash represents BE hash of native Policy contract. -var PolicyContractHash = util.Uint160{154, 97, 164, 110, 236, 151, 184, 147, 6, 215, 206, 129, 241, 91, 70, 32, 145, 208, 9, 50} +// PolicyContractHash represents a hash of native Policy contract. +var PolicyContractHash, _ = util.Uint160DecodeStringBE("e9ff4ca7cc252e1dfddb26315869cd79505906ce") // GetMaxTransactionsPerBlock invokes `getMaxTransactionsPerBlock` method on a // native Policy contract. diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 7b3635a7e..a9a0d37bc 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -52,7 +52,7 @@ type rpcTestCase struct { } const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672" -const deploymentTxHash = "dcf4fe429ec84947361c86c2192b14641be7f0c6e2bdf8d150fad731160ed386" +const deploymentTxHash = "ef4209bc06e1d8412995c645a8497d3e2c9a05ca52236de94297c6db9c3e94d0" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index be4676b9f526d99c8c21b228d9b09af0b759a51d..16abe186d63bc47e17f32a98054f9be6f0323d21 100644 GIT binary patch delta 2848 zcmaKtXIqmA8ihkm0xv~GdNa~N2aw);0qIH!MLH2fk2FELA%Z#}Js?GzU4AEJ;hne#cQ(@Oa9DG>wqTk zO+`1~GmXO;okn$29Z069m?5xCm{?Rl2X3>;48#qgm5wHua|EYk+RyjNXq-Gj(x>%h znOqua)OMKXh!IG2d})GIkn#Gy#91I%c1G(Ro^c%jU-*oCAj~apH?tlGA2tL8U_FHU zyxT>0A4NN{SgpSOi!H)&dBxMZwCb#cZ=a5B@%|54M6)tCIb9a1$BLb03I@Q`VlFN< zgV$;O$S2LuL^k%4ETOHohb*wq`Dj2ZVRjm$`7y)*9vz>Urz7a4vR*syztELSb4{ek zC-C=FznDA#^oi!ih-ze7$CyohSBy3b8fkIBnm5XMX1WrPW^11n)=^_dU+S4cCQ#w` z92WF@Ev4O2rcV$$nlkx2|>3AmUM63v1UcW)eSV_rU7T!TGhoYJG+hvW6q@mX7+DlK7QDN6DHAi1aS+JlD7L{zB^p(V zjnq`B<1gsLKA(~_rget1@HGxp7Te6c|J}1pr9kF^uS|U_0CHpm%;JPg!tYyj*tGi+ zhIs7-jR3D2;gENF0V`cd{;f@9+rlPh@s%_1u|Lawnh3){p%Ro__oER2 zr|@M})>Uz8!uD>eZKM(8RMvQ)<3M=o`K70$@81KqBfv;!4;Dh11cy&Mp z&&7BX4`m>bf`CqHkhGu}gW>q|)(+)rN-vCzwZye)QAPqR`C7l6rN1zdBI}Ek8(mC@ zbBLgqGp%j);m>$~LACp@@Ce(`c1`gD4?OOIr1&cJzXRoG-&rv>bj=E>$u&5XGwJej z%?Pf6Rew%eR;v`^&=|w_u?W#_{7enM&4R$rD&B^0bgS~oxe#ZE2|8!Oq4^f}gJE<7 zwFKW!4HIM1_2z>9cW_wWcr=i7Nyd&M%KZAYeO`p+a!Ea7Xw5kQe4M2E*seC}gTzkk zmt6W=wz7&Ha?On$Vmfy%?(jV8Pj>$1{`zaqjS}n5#S%w$U0nz(9x|TN@x~+bs?-_w zBJ>CVuWN)TPoCy0^BNvVUUs@rre%@aBN_xW>b? z`S7LOPji8$Rz$eYxrUu75CBUzHy&cd#%DxTp zs~T`#mQhU>R19mBj`HIwc`mZ27@3=q;^t99fKG6QZ0!&O@-**M!OMO zZ!1dMa#rdPatnv)YVj>ynzMxE_G?XiC3>&N1PI5Na6d0b7o)*G5CA}R{eyNIw>CF( zT~%UIEF7z>Umm=7_nKMSYML!C$-Qr-v&w;*leMtPQvtPvx@>hs>cliS*CxVD#My(t zjl)p?RiTg_=k$Lbnl=EoRM!27D4xOp-mEtt$S&Ra^x3#sV?nWR;Z!=~53pAN3|VlL>9djys) zEBmskS^A68?k+2e0I0XXJsXh9m6U8yOiQ-we|=*;lc%g8VN=cdNg^XN~ZUp5`wAwjHd(|b-$?| z2T!`rN{5Pi55Hx$BVy7)ez)TE9TDj3Uf0)B_!LrdbO68)OMd+v3m%G;;&BMO--Ecs3RPvlVM2;hmyP6 zPA9)oWTIvywQ`P92}RGSV5Ba5iqdZ<3Bl*yL8oaa@v#pmZMsW=n_iKZp!jNTA^7$` zAnAXB^rP?;#!{39(Yei4>|C5wOg5$Ch!Z2tJTLk4PZH$9=#?P1(rg32UmEEUy$bia zvzK~P{QGtMjQdS=^sw<-2LKYd{q1NZ-9mvI4XwRKr(~k31Po}8H_LOhx1?`{3q|Y@ zc?cOj1zvudb}oU?K!FPpKB6{z-+9nt03J)eoeY58y$ghNWq$#9$G3GOU!<*8ZMRV7 zIpa#{Ra?gcj(tyIJ{NmSZReXC$i5opo&x1ua|+|OtCCdGNGwnClM)U9^cPr@zYx0U z)x-upAjM&4tTw|v7-V~n9R+f=QUd@0 delta 2848 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 ZqeR%`z|#az%rmWe