From 425277098cb4ebfc9fefa74e17568ef46c8f33c8 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 10 Jun 2020 14:23:25 +0300 Subject: [PATCH 01/10] native: set ContractID for NEO & GAS contracts --- pkg/core/interop/context.go | 1 + pkg/core/native/native_gas.go | 2 ++ pkg/core/native/native_neo.go | 2 ++ 3 files changed, 5 insertions(+) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index b1e400624..5d96a9493 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -82,6 +82,7 @@ type ContractMD struct { Manifest manifest.Manifest ServiceName string ServiceID uint32 + ContractID int32 Script []byte Hash util.Uint160 Methods map[string]MethodAndPrice diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 109deceb3..ea98b80f3 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -20,6 +20,7 @@ type GAS struct { } const gasSyscallName = "Neo.Native.Tokens.GAS" +const gasContractID = -2 // GASFactor is a divisor for finding GAS integral value. const GASFactor = NEOTotalSupply @@ -35,6 +36,7 @@ func NewGAS() *GAS { nep5.factor = GASFactor nep5.onPersist = chainOnPersist(g.onPersist, g.OnPersist) nep5.incBalance = g.increaseBalance + nep5.ContractID = gasContractID g.nep5TokenNative = *nep5 diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 84b85cf74..2dfd4b2f6 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -34,6 +34,7 @@ type keyWithVotes struct { const ( neoSyscallName = "Neo.Native.Tokens.NEO" + neoContractID = -2 // NEOTotalSupply is the total amount of NEO in the system. NEOTotalSupply = 100000000 // prefixValidator is a prefix used to store validator's data. @@ -69,6 +70,7 @@ func NewNEO() *NEO { nep5.factor = 1 nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist) nep5.incBalance = n.increaseBalance + nep5.ContractID = neoContractID n.nep5TokenNative = *nep5 From df958caf93bcd51531b0bc80ba6e1d9d1fff61da Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 9 Jun 2020 12:12:56 +0300 Subject: [PATCH 02/10] core: add Manifest to state.Contract --- pkg/core/dao/cacheddao_test.go | 18 +++- pkg/core/dao/dao_test.go | 4 +- pkg/core/gas_price.go | 5 - pkg/core/gas_price_test.go | 60 ------------ pkg/core/helper_test.go | 24 ++--- pkg/core/interop/context.go | 3 +- pkg/core/interop_neo.go | 110 ++++++++++------------ pkg/core/interop_neo_test.go | 23 +++-- pkg/core/native/interop.go | 6 +- pkg/core/state/contract.go | 74 +++++++++------ pkg/core/state/contract_test.go | 64 +++++++------ pkg/interop/contract/contract.go | 35 +------ pkg/rpc/client/rpc.go | 5 +- pkg/rpc/client/rpc_test.go | 42 ++++----- pkg/rpc/response/result/contract_state.go | 53 ----------- pkg/rpc/server/server.go | 5 +- pkg/rpc/server/server_test.go | 15 ++- pkg/rpc/server/testdata/testblocks.acc | Bin 6516 -> 6784 bytes pkg/smartcontract/manifest/manifest.go | 21 +++++ 19 files changed, 229 insertions(+), 338 deletions(-) delete mode 100644 pkg/rpc/response/result/contract_state.go diff --git a/pkg/core/dao/cacheddao_test.go b/pkg/core/dao/cacheddao_test.go index f8c2bdf75..bd79af520 100644 --- a/pkg/core/dao/cacheddao_test.go +++ b/pkg/core/dao/cacheddao_test.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -59,10 +60,19 @@ func TestCachedDaoContracts(t *testing.T) { _, err := dao.GetContractState(sh) require.NotNil(t, err) - cs := &state.Contract{} - cs.Name = "test" - cs.Script = script - cs.ParamList = []smartcontract.ParamType{1, 2} + m := manifest.NewManifest(hash.Hash160(script)) + m.ABI.EntryPoint.Name = "somename" + m.ABI.EntryPoint.Parameters = []manifest.Parameter{ + manifest.NewParameter("first", smartcontract.IntegerType), + manifest.NewParameter("second", smartcontract.StringType), + } + m.ABI.EntryPoint.ReturnType = smartcontract.BoolType + + cs := &state.Contract{ + ID: 123, + Script: script, + Manifest: *m, + } require.NoError(t, dao.PutContractState(cs)) cs2, err := dao.GetContractState(sh) diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index 640620a11..ce29491f1 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -60,7 +60,7 @@ func TestPutAndGetAccountStateOrNew(t *testing.T) { func TestPutAndGetContractState(t *testing.T) { dao := NewSimple(storage.NewMemoryStore()) - contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}} + contractState := &state.Contract{Script: []byte{}} hash := contractState.ScriptHash() err := dao.PutContractState(contractState) require.NoError(t, err) @@ -71,7 +71,7 @@ func TestPutAndGetContractState(t *testing.T) { func TestDeleteContractState(t *testing.T) { dao := NewSimple(storage.NewMemoryStore()) - contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}} + contractState := &state.Contract{Script: []byte{}} hash := contractState.ScriptHash() err := dao.PutContractState(contractState) require.NoError(t, err) diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index 4d67955d2..e7318b52e 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -1,7 +1,6 @@ package core import ( - "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/opcode" @@ -41,8 +40,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 { } const ( - neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create - neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate systemStoragePut = 0x84183fe6 // System.Storage.Put systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx neoStoragePut = 0xf541a152 // Neo.Storage.Put @@ -51,8 +48,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 { estack := v.Estack() switch id { - case neoContractCreate, neoContractMigrate: - return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64())) case systemStoragePut, systemStoragePutEx, neoStoragePut: // price for storage PUT is 1 GAS per 1 KiB keySize := len(estack.Peek(1).Bytes()) diff --git a/pkg/core/gas_price_test.go b/pkg/core/gas_price_test.go index 87faae008..f618a949c 100644 --- a/pkg/core/gas_price_test.go +++ b/pkg/core/gas_price_test.go @@ -23,66 +23,6 @@ func TestGetPrice(t *testing.T) { v := SpawnVM(systemInterop) v.SetPriceGetter(getPrice) - t.Run("Neo.Contract.Create (no props)", func(t *testing.T) { - // Neo.Contract.Create: f66ca56e (requires push properties on fourth position) - v.Load([]byte{byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), - byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e}) - require.NoError(t, v.StepInto()) // push 0 - ContractPropertyState.NoProperty - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - - checkGas(t, util.Fixed8FromInt64(100), v) - }) - - t.Run("Neo.Contract.Create (has storage)", func(t *testing.T) { - // Neo.Contract.Create: f66ca56e (requires push properties on fourth position) - v.Load([]byte{byte(opcode.PUSH1), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), - byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e}) - require.NoError(t, v.StepInto()) // push 01 - ContractPropertyState.HasStorage - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - - checkGas(t, util.Fixed8FromInt64(500), v) - }) - - t.Run("Neo.Contract.Create (has dynamic invoke)", func(t *testing.T) { - // Neo.Contract.Create: f66ca56e (requires push properties on fourth position) - v.Load([]byte{byte(opcode.PUSH2), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), - byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e}) - require.NoError(t, v.StepInto()) // push 02 - ContractPropertyState.HasDynamicInvoke - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - - checkGas(t, util.Fixed8FromInt64(600), v) - }) - - t.Run("Neo.Contract.Create (has both storage and dynamic invoke)", func(t *testing.T) { - // Neo.Contract.Create: f66ca56e (requires push properties on fourth position) - v.Load([]byte{byte(opcode.PUSH3), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), - byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e}) - require.NoError(t, v.StepInto()) // push 03 - HasStorage and HasDynamicInvoke - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - - checkGas(t, util.Fixed8FromInt64(1000), v) - }) - - t.Run("Neo.Contract.Migrate", func(t *testing.T) { - // Neo.Contract.Migrate: 471b6290 (requires push properties on fourth position) - v.Load([]byte{byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), - byte(opcode.SYSCALL), 0x47, 0x1b, 0x62, 0x90}) - require.NoError(t, v.StepInto()) // push 0 - ContractPropertyState.NoProperty - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - require.NoError(t, v.StepInto()) // push 0 - - checkGas(t, util.Fixed8FromInt64(100), v) - }) - t.Run("System.Storage.Put", func(t *testing.T) { // System.Storage.Put: e63f1884 (requires push key and value) v.Load([]byte{byte(opcode.PUSH3), byte(opcode.PUSH3), byte(opcode.PUSH0), diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 06e394ca0..f8a20f059 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -18,6 +18,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "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/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -222,19 +223,18 @@ func TestCreateBasicChain(t *testing.T) { require.NoError(t, err) t.Logf("contractHash: %s", hash.Hash160(avm).StringLE()) - var props smartcontract.PropertyState script := io.NewBufBinWriter() - emit.Bytes(script.BinWriter, []byte("Da contract dat hallos u")) - emit.Bytes(script.BinWriter, []byte("joe@example.com")) - emit.Bytes(script.BinWriter, []byte("Random Guy")) - emit.Bytes(script.BinWriter, []byte("0.99")) - emit.Bytes(script.BinWriter, []byte("Helloer")) - props |= smartcontract.HasStorage - emit.Int(script.BinWriter, int64(props)) - emit.Int(script.BinWriter, int64(5)) - params := make([]byte, 1) - params[0] = byte(7) - emit.Bytes(script.BinWriter, params) + m := manifest.NewManifest(hash.Hash160(avm)) + m.ABI.EntryPoint.Name = "Main" + m.ABI.EntryPoint.Parameters = []manifest.Parameter{ + manifest.NewParameter("method", smartcontract.StringType), + manifest.NewParameter("params", smartcontract.ArrayType), + } + m.ABI.EntryPoint.ReturnType = smartcontract.BoolType + m.Features = smartcontract.HasStorage + bs, err := testserdes.EncodeBinary(m) + require.NoError(t, err) + emit.Bytes(script.BinWriter, bs) emit.Bytes(script.BinWriter, avm) emit.Syscall(script.BinWriter, "Neo.Contract.Create") txScript := script.Bytes() diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 5d96a9493..026045baa 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -94,8 +94,7 @@ func (ic *Context) GetContract(h util.Uint160) ([]byte, bool) { if err != nil { return nil, false } - hasDynamicInvoke := (cs.Properties & smartcontract.HasDynamicInvoke) != 0 - return cs.Script, hasDynamicInvoke + return cs.Script, cs.HasDynamicInvoke() } // NewContractMD returns Contract with the specified list of methods. diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index a79757cb7..08f07de56 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -8,7 +8,8 @@ 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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/io" + "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/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -68,48 +69,20 @@ func createContractStateFromVM(ic *interop.Context, v *vm.VM) (*state.Contract, if len(script) > MaxContractScriptSize { return nil, errors.New("the script is too big") } - paramBytes := v.Estack().Pop().Bytes() - if len(paramBytes) > MaxContractParametersNum { - return nil, errors.New("too many parameters for a script") + manifestBytes := v.Estack().Pop().Bytes() + if len(manifestBytes) > manifest.MaxManifestSize { + return nil, errors.New("manifest is too big") } - paramList := make([]smartcontract.ParamType, len(paramBytes)) - for k, v := range paramBytes { - paramList[k] = smartcontract.ParamType(v) + var m manifest.Manifest + r := io.NewBinReaderFromBuf(manifestBytes) + m.DecodeBinary(r) + if r.Err != nil { + return nil, r.Err } - retType := smartcontract.ParamType(v.Estack().Pop().BigInt().Int64()) - properties := smartcontract.PropertyState(v.Estack().Pop().BigInt().Int64()) - name := v.Estack().Pop().Bytes() - if len(name) > MaxContractStringLen { - return nil, errors.New("too big name") - } - version := v.Estack().Pop().Bytes() - if len(version) > MaxContractStringLen { - return nil, errors.New("too big version") - } - author := v.Estack().Pop().Bytes() - if len(author) > MaxContractStringLen { - return nil, errors.New("too big author") - } - email := v.Estack().Pop().Bytes() - if len(email) > MaxContractStringLen { - return nil, errors.New("too big email") - } - desc := v.Estack().Pop().Bytes() - if len(desc) > MaxContractDescriptionLen { - return nil, errors.New("too big description") - } - contract := &state.Contract{ - Script: script, - ParamList: paramList, - ReturnType: retType, - Properties: properties, - Name: string(name), - CodeVersion: string(version), - Author: string(author), - Email: string(email), - Description: string(desc), - } - return contract, nil + return &state.Contract{ + Script: script, + Manifest: m, + }, nil } // contractCreate creates a contract. @@ -119,14 +92,12 @@ func contractCreate(ic *interop.Context, v *vm.VM) error { return err } contract, err := ic.DAO.GetContractState(newcontract.ScriptHash()) - if err != nil { - contract = newcontract - err := ic.DAO.PutContractState(contract) - if err != nil { - return err - } + if contract != nil { + return errors.New("contract already exists") + } else if err := ic.DAO.PutContractState(newcontract); err != nil { + return err } - v.Estack().PushVal(stackitem.NewInterop(contract)) + v.Estack().PushVal(stackitem.NewInterop(newcontract)) return nil } @@ -154,30 +125,45 @@ func contractIsPayable(ic *interop.Context, v *vm.VM) error { // contractMigrate migrates a contract. func contractMigrate(ic *interop.Context, v *vm.VM) error { + contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) + if contract == nil { + return errors.New("contract doesn't exist") + } newcontract, err := createContractStateFromVM(ic, v) if err != nil { return err } - contract, err := ic.DAO.GetContractState(newcontract.ScriptHash()) - if err != nil { - contract = newcontract - err := ic.DAO.PutContractState(contract) + if newcontract.Script != nil { + if l := len(newcontract.Script); l == 0 || l > MaxContractScriptSize { + return errors.New("invalid script len") + } + h := newcontract.ScriptHash() + if h.Equals(contract.ScriptHash()) { + return errors.New("the script is the same") + } else if _, err := ic.DAO.GetContractState(h); err == nil { + return errors.New("contract already exists") + } + newcontract.ID = contract.ID + if err := ic.DAO.PutContractState(newcontract); err != nil { + return err + } + if err := ic.DAO.DeleteContractState(contract.ScriptHash()); err != nil { + return err + } + } + if contract.HasStorage() { + // TODO store items by ID #1037 + hash := v.GetCurrentScriptHash() + siMap, err := ic.DAO.GetStorageItems(hash) if err != nil { return err } - if contract.HasStorage() { - hash := v.GetCurrentScriptHash() - siMap, err := ic.DAO.GetStorageItems(hash) + for k, v := range siMap { + v.IsConst = false + err = ic.DAO.PutStorageItem(contract.ScriptHash(), []byte(k), v) if err != nil { return err } - for k, v := range siMap { - v.IsConst = false - err = ic.DAO.PutStorageItem(contract.ScriptHash(), []byte(k), v) - if err != nil { - return err - } - } } } v.Estack().PushVal(stackitem.NewInterop(contract)) diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 853e9850d..0170cb00e 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -13,9 +13,10 @@ import ( "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/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/internal/random" "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" @@ -265,16 +266,18 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) { v := vm.New() + script := []byte("testscript") + m := manifest.NewManifest(hash.Hash160(script)) + m.ABI.EntryPoint.Parameters = []manifest.Parameter{ + manifest.NewParameter("Name", smartcontract.StringType), + manifest.NewParameter("Amount", smartcontract.IntegerType), + manifest.NewParameter("Hash", smartcontract.Hash160Type), + } + m.ABI.EntryPoint.ReturnType = smartcontract.ArrayType + m.Features = smartcontract.HasStorage contractState := &state.Contract{ - Script: []byte("testscript"), - ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type}, - ReturnType: smartcontract.ArrayType, - Properties: smartcontract.HasStorage, - Name: random.String(10), - CodeVersion: random.String(10), - Author: random.String(10), - Email: random.String(10), - Description: random.String(10), + Script: script, + Manifest: *m, } chain := newTestChain(t) diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 24a6a28b4..c36200acf 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -26,9 +26,9 @@ func Deploy(ic *interop.Context, _ *vm.VM) error { } cs := &state.Contract{ - Script: md.Script, - ParamList: params, - ReturnType: md.Manifest.ABI.EntryPoint.ReturnType, + ID: md.ContractID, + Script: md.Script, + Manifest: md.Manifest, } if err := ic.DAO.PutContractState(cs); err != nil { return err diff --git a/pkg/core/state/contract.go b/pkg/core/state/contract.go index 33e791fd3..86e9a231d 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -1,52 +1,38 @@ package state import ( + "encoding/json" + "errors" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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/util" ) // Contract holds information about a smart contract in the NEO blockchain. type Contract struct { - Script []byte - ParamList []smartcontract.ParamType - ReturnType smartcontract.ParamType - Properties smartcontract.PropertyState - Name string - CodeVersion string - Author string - Email string - Description string + ID int32 + Script []byte + Manifest manifest.Manifest scriptHash util.Uint160 } // DecodeBinary implements Serializable interface. func (cs *Contract) DecodeBinary(br *io.BinReader) { + cs.ID = int32(br.ReadU32LE()) cs.Script = br.ReadVarBytes() - br.ReadArray(&cs.ParamList) - cs.ReturnType = smartcontract.ParamType(br.ReadB()) - cs.Properties = smartcontract.PropertyState(br.ReadB()) - cs.Name = br.ReadString() - cs.CodeVersion = br.ReadString() - cs.Author = br.ReadString() - cs.Email = br.ReadString() - cs.Description = br.ReadString() + cs.Manifest.DecodeBinary(br) cs.createHash() } // EncodeBinary implements Serializable interface. func (cs *Contract) EncodeBinary(bw *io.BinWriter) { + bw.WriteU32LE(uint32(cs.ID)) bw.WriteVarBytes(cs.Script) - bw.WriteArray(cs.ParamList) - bw.WriteB(byte(cs.ReturnType)) - bw.WriteB(byte(cs.Properties)) - bw.WriteString(cs.Name) - bw.WriteString(cs.CodeVersion) - bw.WriteString(cs.Author) - bw.WriteString(cs.Email) - bw.WriteString(cs.Description) + cs.Manifest.EncodeBinary(bw) } // ScriptHash returns a contract script hash. @@ -64,15 +50,47 @@ func (cs *Contract) createHash() { // HasStorage checks whether the contract has storage property set. func (cs *Contract) HasStorage() bool { - return (cs.Properties & smartcontract.HasStorage) != 0 + return (cs.Manifest.Features & smartcontract.HasStorage) != 0 } // HasDynamicInvoke checks whether the contract has dynamic invoke property set. func (cs *Contract) HasDynamicInvoke() bool { - return (cs.Properties & smartcontract.HasDynamicInvoke) != 0 + return (cs.Manifest.Features & smartcontract.HasDynamicInvoke) != 0 } // IsPayable checks whether the contract has payable property set. func (cs *Contract) IsPayable() bool { - return (cs.Properties & smartcontract.IsPayable) != 0 + return (cs.Manifest.Features & smartcontract.IsPayable) != 0 +} + +type contractJSON struct { + ID int32 `json:"id"` + Script []byte `json:"script"` + Manifest *manifest.Manifest `json:"manifest"` + ScriptHash util.Uint160 `json:"hash"` +} + +// MarshalJSON implements json.Marshaler. +func (cs *Contract) MarshalJSON() ([]byte, error) { + return json.Marshal(&contractJSON{ + ID: cs.ID, + Script: cs.Script, + Manifest: &cs.Manifest, + ScriptHash: cs.ScriptHash(), + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (cs *Contract) UnmarshalJSON(data []byte) error { + var cj contractJSON + if err := json.Unmarshal(data, &cj); err != nil { + return err + } else if cj.Manifest == nil { + return errors.New("empty manifest") + } + cs.ID = cj.ID + cs.Script = cj.Script + cs.Manifest = *cj.Manifest + cs.createHash() + return nil } diff --git a/pkg/core/state/contract_test.go b/pkg/core/state/contract_test.go index 9101dfab1..052f11c39 100644 --- a/pkg/core/state/contract_test.go +++ b/pkg/core/state/contract_test.go @@ -6,42 +6,46 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/stretchr/testify/assert" ) func TestEncodeDecodeContractState(t *testing.T) { script := []byte("testscript") - contract := &Contract{ - Script: script, - ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type}, - ReturnType: smartcontract.BoolType, - Properties: smartcontract.HasStorage, - Name: "Contrato", - CodeVersion: "1.0.0", - Author: "Joe Random", - Email: "joe@example.com", - Description: "Test contract", - } - - assert.Equal(t, hash.Hash160(script), contract.ScriptHash()) - - contractDecoded := &Contract{} - testserdes.EncodeDecodeBinary(t, contract, contractDecoded) - assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash()) -} - -func TestContractStateProperties(t *testing.T) { - flaggedContract := Contract{ - Properties: smartcontract.HasStorage | smartcontract.HasDynamicInvoke | smartcontract.IsPayable, - } - nonFlaggedContract := Contract{ + h := hash.Hash160(script) + m := manifest.NewManifest(h) + m.ABI.Methods = []manifest.Method{{ + Name: "main", + Parameters: []manifest.Parameter{ + { + Name: "amount", + Type: smartcontract.IntegerType, + }, + { + Name: "hash", + Type: smartcontract.Hash160Type, + }, + }, ReturnType: smartcontract.BoolType, + }} + m.Features = smartcontract.HasStorage + contract := &Contract{ + ID: 123, + Script: script, + Manifest: *m, } - assert.Equal(t, true, flaggedContract.HasStorage()) - assert.Equal(t, true, flaggedContract.HasDynamicInvoke()) - assert.Equal(t, true, flaggedContract.IsPayable()) - assert.Equal(t, false, nonFlaggedContract.HasStorage()) - assert.Equal(t, false, nonFlaggedContract.HasDynamicInvoke()) - assert.Equal(t, false, nonFlaggedContract.IsPayable()) + + assert.Equal(t, h, contract.ScriptHash()) + + t.Run("Serializable", func(t *testing.T) { + contractDecoded := new(Contract) + testserdes.EncodeDecodeBinary(t, contract, contractDecoded) + assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash()) + }) + t.Run("JSON", func(t *testing.T) { + contractDecoded := new(Contract) + testserdes.MarshalUnmarshalJSON(t, contract, contractDecoded) + assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash()) + }) } diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go index d220b85e2..4a67f0748 100644 --- a/pkg/interop/contract/contract.go +++ b/pkg/interop/contract/contract.go @@ -33,30 +33,10 @@ func GetStorageContext(c Contract) storage.Context { // Create creates a new contract using a set of input parameters: // script contract's bytecode (limited in length by 1M) -// params contract's input parameter types, one byte per parameter, see -// ParamType in the `smartcontract` package for value -// definitions. Maximum number of parameters: 252. -// returnType return value type, also a ParamType constant -// properties bit field with contract's permissions (storage, dynamic -// invoke, payable), see PropertyState in the `smartcontract` -// package -// name human-readable contract name (no longer than 252 bytes) -// version human-readable contract version (no longer than 252 bytes) -// author contract's author (no longer than 252 bytes) -// email contract's author/support e-mail (no longer than 252 bytes) -// description human-readable contract description (no longer than 64K bytes) +// manifest contract's manifest (limited in length by 2 KiB) // It returns this new created Contract when successful (and fails transaction // if not). It uses `Neo.Contract.Create` syscall. -func Create( - script []byte, - params []byte, - returnType byte, - properties byte, - name, - version, - author, - email, - description string) Contract { +func Create(script []byte, manifest []byte) Contract { return Contract{} } @@ -65,16 +45,7 @@ func Create( // Create. The old contract will be deleted by this call, if it has any storage // associated it will be migrated to the new contract. New contract is returned. // This function uses `Neo.Contract.Migrate` syscall. -func Migrate( - script []byte, - params []byte, - returnType byte, - properties byte, - name, - version, - author, - email, - description string) Contract { +func Migrate(script []byte, manifest []byte) Contract { return Contract{} } diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index d0d572e85..81add1b93 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -6,6 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" + "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/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" @@ -177,10 +178,10 @@ func (c *Client) GetConnectionCount() (int, error) { } // GetContractState queries contract information, according to the contract script hash. -func (c *Client) GetContractState(hash util.Uint160) (*result.ContractState, error) { +func (c *Client) GetContractState(hash util.Uint160) (*state.Contract, error) { var ( params = request.NewRawParams(hash.StringLE()) - resp = &result.ContractState{} + resp = &state.Contract{} ) if err := c.performRequest("getcontractstate", params, resp); err != nil { return resp, err diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index c3c955d12..eccbdb99b 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -2,6 +2,7 @@ package client import ( "context" + "encoding/base64" "encoding/hex" "net/http" "net/http/httptest" @@ -11,12 +12,15 @@ import ( "github.com/gorilla/websocket" "github.com/nspcc-dev/neo-go/pkg/core/block" + "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/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" @@ -293,39 +297,33 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ { name: "positive", invoke: func(c *Client) (interface{}, error) { - hash, err := util.Uint160DecodeStringLE("dc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f") + hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176") if err != nil { panic(err) } return c.GetContractState(hash) }, - serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"version":0,"hash":"0xdc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f","script":"X8VrbHZrAFJ6xGx2a1FSesRhB1dvb2xvbmdsdmtSUnrEA1dOR2x2a1NSesQAbHZrVFJ6xCEDVK5JgiEEbGZu/ruu6b0OtII0acmOdISUqSpx80axpmFsdmtVUnrEbHZrAMMGZGVwbG95h2x2a1ZSesRsdmtWw2QWAGx2a1XDYWXyAmx2a1dSesRi2AFsdmtVw2Fl2AFhbHZrAMMLdG90YWxTdXBwbHmHbHZrWFJ6xGx2a1jDZEAAYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dAZzdXBwbHlhfGgPTmVvLlN0b3JhZ2UuR2V0bHZrV1J6xGJwAWx2awDDBG5hbWWHbHZrWVJ6xGx2a1nDZBIAbHZrUsNsdmtXUnrEYkcBbHZrAMMGc3ltYm9sh2x2a1pSesRsdmtaw2QSAGx2a1PDbHZrV1J6xGIcAWx2awDDCGRlY2ltYWxzh2x2a1tSesRsdmtbw2QSAGx2a1TDbHZrV1J6xGLvAGx2awDDCWJhbGFuY2VPZodsdmtcUnrEbHZrXMNkQABhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0bHZrUcNRw2F8aA9OZW8uU3RvcmFnZS5HZXRsdmtXUnrEYpMAbHZrUcMAw2FoGE5lby5SdW50aW1lLkNoZWNrV2l0bmVzcwCcbHZrXVJ6xGx2a13DZA4AAGx2a1dSesRiVQBsdmsAwwh0cmFuc2ZlcodsdmteUnrEbHZrXsNkLABsdmtRwwDDbHZrUcNRw2x2a1HDUsNhZdQDYVJyZckBbHZrV1J6xGIOAABsdmtXUnrEYgMAbHZrV8NhbHVmU8VrbHZrAFJ6xGFhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0bHZrAMNhfGgPTmVvLlN0b3JhZ2UuR2V0YWVwA1GTbHZrUVJ6xGFoFk5lby5TdG9yYWdlLkdldENvbnRleHRsdmsAw2x2a1HDYWURA2FScmgPTmVvLlN0b3JhZ2UuUHV0YWFoFk5lby5TdG9yYWdlLkdldENvbnRleHQGc3VwcGx5YXxoD05lby5TdG9yYWdlLkdldGFl9AJRk2x2a1JSesRhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0BnN1cHBseWx2a1LDYWWTAmFScmgPTmVvLlN0b3JhZ2UuUHV0YWFsdWZTxWtsdmsAUnrEYVFsdmtRUnrEYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dGx2awDDbHZrUcNhZUACYVJyaA9OZW8uU3RvcmFnZS5QdXRhYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dAZzdXBwbHlsdmtRw2FlAgJhUnJoD05lby5TdG9yYWdlLlB1dGFRbHZrUlJ6xGIDAGx2a1LDYWx1ZlnFa2x2awBSesRsdmtRUnrEbHZrUlJ6xGFhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0bHZrAMNhfGgPTmVvLlN0b3JhZ2UuR2V0bHZrU1J6xGFoFk5lby5TdG9yYWdlLkdldENvbnRleHRsdmtRw2F8aA9OZW8uU3RvcmFnZS5HZXRsdmtUUnrEbHZrU8NhZXYBbHZrUsOUbHZrVVJ6xGx2a1TDYWVgAWx2a1LDk2x2a1ZSesRsdmtVwwCiZA0AbHZrUsMAomIEAABsdmtXUnrEbHZrV8Nk7ABhYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dGx2awDDbHZrVcNhZdgAYVJyaA9OZW8uU3RvcmFnZS5QdXRhYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dGx2a1HDbHZrVsNhZZwAYVJyaA9OZW8uU3RvcmFnZS5QdXRhVcV2ABNUcmFuc2ZlciBTdWNjZXNzZnVsxHZRbHZrAMPEdlJsdmtRw8R2U2x2a1LDxHZUYWgYTmVvLkJsb2NrY2hhaW4uR2V0SGVpZ2h0xGFoEk5lby5SdW50aW1lLk5vdGlmeWFRbHZrWFJ6xGIOAABsdmtYUnrEYgMAbHZrWMNhbHVmU8VrbHZrAFJ6xGFsdmsAw2x2a1FSesRsdmtRw2x2a1JSesRiAwBsdmtSw2FsdWZTxWtsdmsAUnrEYVFsdmsAw2pSelJ6xGx2a1HDbHZrUlJ6xGIDAGx2a1LDYWx1Zg==","parameters":["ByteArray"],"returntype":"ByteArray","name":"Woolong","code_version":"0.9.2","author":"lllwvlvwlll","email":"lllwvlvwlll@gmail.com","description":"GO NEO!!!","properties":{"storage":true,"dynamic_invoke":false}}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","entryPoint":{"name":"Main","parameters":[{"name":"method","type":"String"},{"name":"params","type":"Array"}],"returnType":"Boolean"},"methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"safeMethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { - hash, err := util.Uint160DecodeStringLE("dc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f") + script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { panic(err) } - script, err := hex.DecodeString("5fc56b6c766b00527ac46c766b51527ac46107576f6f6c6f6e676c766b52527ac403574e476c766b53527ac4006c766b54527ac4210354ae498221046c666efebbaee9bd0eb4823469c98e748494a92a71f346b1a6616c766b55527ac46c766b00c3066465706c6f79876c766b56527ac46c766b56c36416006c766b55c36165f2026c766b57527ac462d8016c766b55c36165d801616c766b00c30b746f74616c537570706c79876c766b58527ac46c766b58c36440006168164e656f2e53746f726167652e476574436f6e7465787406737570706c79617c680f4e656f2e53746f726167652e4765746c766b57527ac46270016c766b00c3046e616d65876c766b59527ac46c766b59c36412006c766b52c36c766b57527ac46247016c766b00c30673796d626f6c876c766b5a527ac46c766b5ac36412006c766b53c36c766b57527ac4621c016c766b00c308646563696d616c73876c766b5b527ac46c766b5bc36412006c766b54c36c766b57527ac462ef006c766b00c30962616c616e63654f66876c766b5c527ac46c766b5cc36440006168164e656f2e53746f726167652e476574436f6e746578746c766b51c351c3617c680f4e656f2e53746f726167652e4765746c766b57527ac46293006c766b51c300c36168184e656f2e52756e74696d652e436865636b5769746e657373009c6c766b5d527ac46c766b5dc3640e00006c766b57527ac46255006c766b00c3087472616e73666572876c766b5e527ac46c766b5ec3642c006c766b51c300c36c766b51c351c36c766b51c352c36165d40361527265c9016c766b57527ac4620e00006c766b57527ac46203006c766b57c3616c756653c56b6c766b00527ac4616168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e4765746165700351936c766b51527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b51c361651103615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e7465787406737570706c79617c680f4e656f2e53746f726167652e4765746165f40251936c766b52527ac46168164e656f2e53746f726167652e476574436f6e7465787406737570706c796c766b52c361659302615272680f4e656f2e53746f726167652e50757461616c756653c56b6c766b00527ac461516c766b51527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b51c361654002615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e7465787406737570706c796c766b51c361650202615272680f4e656f2e53746f726167652e50757461516c766b52527ac46203006c766b52c3616c756659c56b6c766b00527ac46c766b51527ac46c766b52527ac4616168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e4765746c766b53527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b51c3617c680f4e656f2e53746f726167652e4765746c766b54527ac46c766b53c3616576016c766b52c3946c766b55527ac46c766b54c3616560016c766b52c3936c766b56527ac46c766b55c300a2640d006c766b52c300a2620400006c766b57527ac46c766b57c364ec00616168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b55c36165d800615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e746578746c766b51c36c766b56c361659c00615272680f4e656f2e53746f726167652e5075746155c57600135472616e73666572205375636365737366756cc476516c766b00c3c476526c766b51c3c476536c766b52c3c476546168184e656f2e426c6f636b636861696e2e476574486569676874c46168124e656f2e52756e74696d652e4e6f7469667961516c766b58527ac4620e00006c766b58527ac46203006c766b58c3616c756653c56b6c766b00527ac4616c766b00c36c766b51527ac46c766b51c36c766b52527ac46203006c766b52c3616c756653c56b6c766b00527ac461516c766b00c36a527a527ac46c766b51c36c766b52527ac46203006c766b52c3616c7566") - if err != nil { - panic(err) + m := manifest.NewManifest(hash.Hash160(script)) + m.ABI.EntryPoint.Name = "Main" + m.ABI.EntryPoint.Parameters = []manifest.Parameter{ + manifest.NewParameter("method", smartcontract.StringType), + manifest.NewParameter("params", smartcontract.ArrayType), } - return &result.ContractState{ - Version: 0, - ScriptHash: hash, - Script: script, - ParamList: []smartcontract.ParamType{smartcontract.ByteArrayType}, - ReturnType: smartcontract.ByteArrayType, - Name: "Woolong", - CodeVersion: "0.9.2", - Author: "lllwvlvwlll", - Email: "lllwvlvwlll@gmail.com", - Description: "GO NEO!!!", - Properties: result.Properties{ - HasStorage: true, - HasDynamicInvoke: false, - IsPayable: false, - }, + m.ABI.EntryPoint.ReturnType = smartcontract.BoolType + m.Features = smartcontract.HasStorage + cs := &state.Contract{ + ID: 0, + Script: script, + Manifest: *m, } + _ = cs.ScriptHash() + return cs }, }, }, diff --git a/pkg/rpc/response/result/contract_state.go b/pkg/rpc/response/result/contract_state.go deleted file mode 100644 index 2d3f36fd5..000000000 --- a/pkg/rpc/response/result/contract_state.go +++ /dev/null @@ -1,53 +0,0 @@ -package result - -import ( - "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/util" -) - -// ContractState wrapper used for the representation of -// state.Contract on the RPC Server. -type ContractState struct { - Version byte `json:"version"` - ScriptHash util.Uint160 `json:"hash"` - Script []byte `json:"script"` - ParamList []smartcontract.ParamType `json:"parameters"` - ReturnType smartcontract.ParamType `json:"returntype"` - Name string `json:"name"` - CodeVersion string `json:"code_version"` - Author string `json:"author"` - Email string `json:"email"` - Description string `json:"description"` - Properties Properties `json:"properties"` -} - -// Properties response wrapper. -type Properties struct { - HasStorage bool `json:"storage"` - HasDynamicInvoke bool `json:"dynamic_invoke"` - IsPayable bool `json:"is_payable"` -} - -// NewContractState creates a new Contract wrapper. -func NewContractState(c *state.Contract) ContractState { - properties := Properties{ - HasStorage: c.HasStorage(), - HasDynamicInvoke: c.HasDynamicInvoke(), - IsPayable: c.IsPayable(), - } - - return ContractState{ - Version: 0, - ScriptHash: c.ScriptHash(), - Script: c.Script, - ParamList: c.ParamList, - ReturnType: c.ReturnType, - Properties: properties, - Name: c.Name, - CodeVersion: c.CodeVersion, - Author: c.Author, - Email: c.Email, - Description: c.Description, - } -} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index c42ddf6ed..c75af6fad 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -738,11 +738,10 @@ func (s *Server) getContractState(reqParams request.Params) (interface{}, *respo return nil, response.ErrInvalidParams } else { cs := s.chain.GetContractState(scriptHash) - if cs != nil { - results = result.NewContractState(cs) - } else { + if cs == nil { return nil, response.NewRPCError("Unknown contract", "", nil) } + results = cs } return results, nil } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index c10180304..bcc28e0f5 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -17,6 +17,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" + "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/encoding/address" "github.com/nspcc-dev/neo-go/pkg/internal/testchain" @@ -55,12 +56,12 @@ var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { name: "positive", - params: `["328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd"]`, + params: `["9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7"]`, result: func(e *executor) interface{} { return &result.ApplicationLog{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.ApplicationLog) require.True(t, ok) - expectedTxHash, err := util.Uint256DecodeStringLE("328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd") + expectedTxHash, err := util.Uint256DecodeStringLE("9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7") require.NoError(t, err) assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, 1, len(res.Executions)) @@ -88,13 +89,11 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", params: fmt.Sprintf(`["%s"]`, testContractHash), - result: func(e *executor) interface{} { return &result.ContractState{} }, + result: func(e *executor) interface{} { return &state.Contract{} }, check: func(t *testing.T, e *executor, cs interface{}) { - res, ok := cs.(*result.ContractState) + res, ok := cs.(*state.Contract) require.True(t, ok) - assert.Equal(t, byte(0), res.Version) - assert.Equal(t, testContractHash, res.ScriptHash.StringLE()) - assert.Equal(t, "0.99", res.CodeVersion) + assert.Equal(t, testContractHash, res.ScriptHash().StringLE()) }, }, { @@ -484,7 +483,7 @@ var rpcTestCases = map[string][]rpcTestCase{ "gettransactionheight": { { name: "positive", - params: `["328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd"]`, + params: `["9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7"]`, result: func(e *executor) interface{} { h := 0 return &h diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index bb02750f547480a62d4c1832ad2c833b447d67e3..39cd4d68c1e202b51e21b1a945c199c9f359007d 100644 GIT binary patch delta 2426 zcmYLKdpwi-AGZmMXQYEJaxgi$m1~CM@|Yv!x~7IBHQQrhZES>T) zsFhpDT_`FdVdzkD2&Z#ga+Z4ie$PM8^Lak+_xt<)zQ3>U_q%9vX8(0J$$NR@DV|#F z?p5HZvW)9LUCmMYbygF4o169y`OMyI_vdVl%5ylvZ4x{#xtVT_F(RLxSUSu2N>S|O zH8<6VKyu!_g$heg0g&eDY)?$q%2GDXdAW~Bf}USHG>~~C8cSgkZzu2*ZDjCcSL_1a zMFn_|XGurGXqP5zjKXyXj|3z-9*kqV<^cfs+r`eCQ@Z}1lv3YZWVYhjleM}ft^?WG zEvrFb>%wrk^;eRu@Gp6umGjoUm|VQu#i!xCt0h9x1)X2IK3!gy20&(7n&K^5{VGO1 zV?bRgluL^(r_Ou6Lun=j^!3+vUv?=UA6!p%EVM8@WCC{!?@L1u@Z)Pq`G{gSg8?D> za3iNdGPME;zp@ECDJ`+_JY5t%_mO=(9&oquLGRnn))iz-^&KwDZugVbo~@T#rOQW5 z-3G(94__^OqI$aV?S}~=`hgUp`uAHRN7L4i(k_L0<5_f=gan*Zs-VUey#wE-Hb87h z>eMp+U5DZM=eI$TeVq#j4PkcP{xig)-8&0wRr zY-5)SVrAh2W7=Rg`*6=ZmN9SO6G_{WT-YIjXYfyoMgU*);LWPc>SV z))ryX&#=l;1=eE`d71{BcTo;<6sR4`%#?QhgH~gzYb-9pc0NO}vY_%i=f##BP6m>Tg-fl4?Di51)l!BlZCj6fIH z5fGh$vidz1#s1M1|2jbTVc*cW2zoG;jA9x5kH0}={2yUYrxRjOtfK}fIusE} zr+R-UVrVo9M4*b9sPCHJ#u0;{Q({*e-{KCLPKylNDA)$5Ac!F5L*g=4>@J27ND&(d zB2XBR0g4epqZ7#DI)WYviHTtl{bVqM5lo|s;nYY9MQoTJ$@nfTh7*Dy_l=osh>J&! zj-V61BU#9P_z^f>9S$IgG;u%#VuS{fP+_~d8xD3OcG>t~(mBi^G!Wb2w_KB;;(66O z?W4f=x`X8<*1juu*~m3{W)Z(9qIQh#e;@z{PU!!4iVwN+1N9KNxT z62WkX3@2jqWT1uiwnp}+CX2(-uZ&M6f~T~B%8BfD6;3vwAz73+&S?O&*l1^19(A4E zF`RjKr8*qJFtdjga&A@>eW)Hty{)Q}vn<>E#cJel0s6MTR50}e;5IWqkaBml=M8(Z zvb9CE9smXA+-lqNlkV5DCc(_UF2O+Z)}6cMN1Zjo|E!EoE^cwj*#SEp+Q&7+41>EL z-Uvun)5$*fez9s{8yPBK>z}d%K!W#bi$-Tv^glE0{$9prm7|oFF~JTC!y8hoSK5uP zltq=A)a+SaYbwQfWuJpFeRZo}T$rsxe#pBKc5~Oswow3#x`B0M&Rx9nCdardzkzs8 zzY=zUucVnDuY_AT?OwIGoYjUEkQ?e$nyRD};(DH+Tkb#l>`5>ZfoBxYBv;iHZ0v_b z`5}$-y?TL8N>V7+tuc!)-sL79Q!$z`>Lsn$rkiu{Zgn1<3kn*N#p3h|6tvi`b0G=t z80UvNv|N!T(w-bAYxg+pqCHP9R>uxc6Lgmy@=4ECJ)D`f77FyrHQ>7Qs$V=IQS37GCqZ#6hf?;o?c<`Q6D8UD>l?#By6J&3W#{^Ok*sbdQMqv0z#(6v8NNeDzOD3gT^sD{i#!;eW5D?7KyL*iXf@mJ@;?PkCR$zPt)wF+NVdBqRKb%#YvziVq)R?*#2?22FYue6B?Q2uV{*0YTi&C81 zS<-NI+kCZ6D zyhZ!$b(!rrpK`C@>0Q1dhCVi&Gb$RJOMe6@y%A(53uW`qWB=*#`GmQNcSS~_iuWT0 zOAo8ZJ9HE(*IbKnOCuR=(_69L+lLz!6!YVPa*wTq7Jc^gujP9ksCT;wfbl8IzNrYk zH^+LMHZxzszvwpn(io!r-UwNH$hs32Hr9a|a!*$3Pep){7`DK+ScXS61NH&gNe+wG zTG^w$jR5$3kv>q()5A=3cGBxs+o75x_pf`pNPSeQcK&QrjlCqFsl1!X$|_8-q>u zbz;yAwNFv#Hemsa6&+Bs6#(+0&5emi2c^88#pLiBYa|?{ zmjgcvoXJH;(P?W7$~lVLQRnnp9nQm)Q3EtRaABr2w(d#RodV5cHPPt2fA<-k3_xuE zSp(%bm4pmG+&wXz*Q+fwDN50prD1uUI-2fT&g?x;TR!Ph>XDYDuT5+q(`94+r}m@O z9gjHHMWk2u0%Qz855K~-YHsO?^7tBV+AH4rhA3Tfc2%C><^C?}_VUxiFm6MMZ3$qvkfdot01poj50000djgCFX zkN~l*{Pr|O)-#eX;wUancAs$s}(Oar!1F;1_4D_83XRO@|9qZ4^ihK(HxIsLwfum){F#)Y^%+UBbTW9AJgD zM%Ed8X;$ZIPKbN9H!ymR^#JME^Z9lUzd$3^N~{X)OCy+m*{PO zhw}KUQvU7=ItFwyYc_5WSfYo~*PXn{@ateuasLGZoj9A67Oqwbu--&d-!~Uv!qp?o zg9YrY3_u{D!ef=B87m=9J5vJbwy#kyl3iI^WeQX^^~MI^T94;ZhhnlgEdK$!2^|(Z5a!btq34of>aOy00000umk`A008|e0}L2M zVIX5~Zgg^CV{{;7VRRs9VQg$~b0BpL4{C2^KxKGgZE$R5E@N+P3<^?VZe(w5AV+n1 z3+;E!@G{krR31L*5jgq?oONUoD4uF)<%{z8QqNpZ5EFY_5n%G zWednQby2ZNwp6P_$DM6*0084kNj==w68?ws%qYp@#i*&vPx$LAd!jeis(xE_Me-Wul2#gl52PVB<@VtxTN-Gqv>Ujr<0KoAPKa&aGi1i z000D&>INNuWw8?5he++Jehv{Zo&O_Vx-~!R49uOhN(w`&v@sw3?4`a3@Z{fk9Dzl|(l#>b)e!S30E}CH+!4NZ7Ysmy z3`4AG(I-}Ox#ho(>r4j*du`XFG0#r}Ui>^6;1LQjLlLWX5dZH!#f%d`sdKn^*3IFt zWGGk&{d?Y};0QcklfeU(lWiFbvwac~0U-R4E3+x}8Vp(zZY11AGtJ*9$FuGb`DOMs z!~L8u=-rd96CeY%xp0%w6C;0m&ryW0NBsR~3nrwsO%XiQr2_-jf7HF0)!l8$kg%Ol zqdUc`&4BjQOX7zNB!~Y>lvon_K2qv(AZMfBl`XLhK*5AZV%O3~#?^9qpwIj*?2ESa zl-Hsv7=j2b5yhe}oo_7@5xg>_Z}mj7j*FGb@@c?c$VF14s%WAFNW6dNo(K#;^+#-a zvSTYu)SNqE%k&8fAxz}*N3la6vX^cQ&9V}ZPC7-RUU;UmY+Xm2n->)^kmlv2>P`EK z959fdE3?#)3_#?~bfRHLo=)@z3MOdp{yN#xr*ryvIGc5AK6?MT`O*R54M46?#*DnY zLzf<$u{vn1!hIu*#!d(W#9k6EZL)lm!2^}Ej}_ekAo#L(49k0~o`*$yI9%wBSrHF( z1-#nE&T+O+GV)gVjgyQQAOp6!aFeeWBY(9EtYH1STNV-dbC6n;_K?r=T&gr5uoiJg z#^sTI&NZ&R?B!0Gh-Oypm~=`W47vX%2gm7&rF~^tNR-JJ7}^X#FGDAN8n&wUx2~k) z&gCvJo^*`_66r^UgR`39nJl4-qO-0@Wq+^d zO$SK$2_Ux|IU{kLi(o5R8$y Date: Tue, 9 Jun 2020 12:23:14 +0300 Subject: [PATCH 03/10] vm: implement (*VM).AddGas Calculating interop prices can be tricky. It is more convenitent to burn gas inside interops where necessary parameters are popped. --- pkg/core/gas_price.go | 3 +++ pkg/core/interop_neo.go | 4 ++++ pkg/vm/vm.go | 6 ++++++ pkg/vm/vm_test.go | 8 ++++++++ 4 files changed, 21 insertions(+) diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index e7318b52e..b9130b0e1 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -11,6 +11,9 @@ import ( // of 0.001 GAS = Fixed8(10^5). const interopGasRatio = 100000 +// StoragePrice is a price for storing 1 byte of storage. +const StoragePrice = 100000 + // getPrice returns a price for executing op with the provided parameter. // Some SYSCALLs have variable price depending on their arguments. func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 { diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 08f07de56..b80031e28 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "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/stackitem" ) @@ -73,6 +74,9 @@ func createContractStateFromVM(ic *interop.Context, v *vm.VM) (*state.Contract, if len(manifestBytes) > manifest.MaxManifestSize { return nil, errors.New("manifest is too big") } + if !v.AddGas(util.Fixed8(StoragePrice * (len(script) + len(manifestBytes)))) { + return nil, errors.New("gas limit exceeded") + } var m manifest.Manifest r := io.NewBinReaderFromBuf(manifestBytes) m.DecodeBinary(r) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 6d0c36f29..3f933595f 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -136,6 +136,12 @@ func (v *VM) SetGasLimit(max util.Fixed8) { v.gasLimit = max } +// AddGas consumes specified amount of gas. It returns true iff gas limit wasn't exceeded. +func (v *VM) AddGas(gas util.Fixed8) bool { + v.gasConsumed += gas + return v.gasLimit == 0 || v.gasConsumed <= v.gasLimit +} + // Estack returns the evaluation stack so interop hooks can utilize this. func (v *VM) Estack() *Stack { return v.estack diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 746df0494..ae9a06fd0 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -98,6 +98,14 @@ func TestVM_SetPriceGetter(t *testing.T) { }) } +func TestAddGas(t *testing.T) { + v := New() + v.SetGasLimit(10) + require.True(t, v.AddGas(5)) + require.True(t, v.AddGas(5)) + require.False(t, v.AddGas(5)) +} + func TestBytesToPublicKey(t *testing.T) { v := New() cache := v.GetPublicKeys() From 20616cc97fb655a1701c02e9d55e469e87c1304f Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 9 Jun 2020 12:27:53 +0300 Subject: [PATCH 04/10] interop: perform contract checks in CheckWitness --- pkg/core/interop/runtime/witness.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/core/interop/runtime/witness.go b/pkg/core/interop/runtime/witness.go index 094737bef..c152a8eff 100644 --- a/pkg/core/interop/runtime/witness.go +++ b/pkg/core/interop/runtime/witness.go @@ -1,6 +1,7 @@ package runtime import ( + "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/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -13,7 +14,7 @@ import ( // for verifying in the interop context. func CheckHashedWitness(ic *interop.Context, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) { if tx, ok := ic.Container.(*transaction.Transaction); ok { - return checkScope(tx, v, hash) + return checkScope(ic.DAO, tx, v, hash) } // only for non-Transaction types (Block, etc.) @@ -29,7 +30,7 @@ func CheckHashedWitness(ic *interop.Context, v vm.ScriptHashGetter, hash util.Ui return false, nil } -func checkScope(tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) { +func checkScope(d dao.DAO, tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) { for _, c := range tx.Cosigners { if c.Account == hash { if c.Scopes == transaction.Global { @@ -51,13 +52,11 @@ func checkScope(tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Ui } } if c.Scopes&transaction.CustomGroups != 0 { - return true, nil - // TODO: we don't currently have Manifest field in ContractState - /*callingScriptHash := v.GetCallingScriptHash() - if callingScriptHash.Equals(util.Uint160{}){ + callingScriptHash := v.GetCallingScriptHash() + if callingScriptHash.Equals(util.Uint160{}) { return false, nil } - cs, err := ic.DAO.GetContractState(callingScriptHash) + cs, err := d.GetContractState(callingScriptHash) if err != nil { return false, err } @@ -68,7 +67,7 @@ func checkScope(tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Ui return true, nil } } - }*/ + } } return false, nil } From a03af55732643a5fdfd444d639e6daad0d6ebc86 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 9 Jun 2020 15:12:23 +0300 Subject: [PATCH 05/10] core: provide monotonically increasing ID for contracts ID is a contract identifier which doesn't change during migration. It is also used as a part of storage context instead of contract hash. --- pkg/core/dao/dao.go | 23 +++++++++++++++++++++++ pkg/core/dao/dao_test.go | 11 +++++++++++ pkg/core/interop_neo.go | 11 ++++++++++- pkg/core/storage/store.go | 1 + 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 59e6261f8..0166f3f30 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -31,6 +31,7 @@ type DAO interface { GetHeaderHashes() ([]util.Uint256, error) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) + GetNextContractID() (int32, error) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) @@ -45,6 +46,7 @@ type DAO interface { PutCurrentHeader(hashAndIndex []byte) error PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error + PutNextContractID(id int32) error PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutVersion(v string) error StoreAsBlock(block *block.Block) error @@ -169,6 +171,27 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error { return dao.Store.Delete(key) } +// GetNextContractID returns id for the next contract and increases stored id. +func (dao *Simple) GetNextContractID() (int32, error) { + key := storage.SYSContractID.Bytes() + data, err := dao.Store.Get(key) + if err != nil { + if err == storage.ErrKeyNotFound { + err = nil + } + return 0, err + } + return int32(binary.LittleEndian.Uint32(data)), nil +} + +// PutNextContractID sets next contract id to id. +func (dao *Simple) PutNextContractID(id int32) error { + key := storage.SYSContractID.Bytes() + data := make([]byte, 4) + binary.LittleEndian.PutUint32(data, uint32(id)) + return dao.Store.Put(key, data) +} + // -- end contracts. // -- start nep5 balances. diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index ce29491f1..44b10e221 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -82,6 +82,17 @@ func TestDeleteContractState(t *testing.T) { require.Nil(t, gotContractState) } +func TestSimple_GetNextContractID(t *testing.T) { + dao := NewSimple(storage.NewMemoryStore()) + id, err := dao.GetNextContractID() + require.NoError(t, err) + require.EqualValues(t, 0, id) + require.NoError(t, dao.PutNextContractID(10)) + id, err = dao.GetNextContractID() + require.NoError(t, err) + require.EqualValues(t, 10, id) +} + func TestPutGetAppExecResult(t *testing.T) { dao := NewSimple(storage.NewMemoryStore()) hash := random.Uint256() diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index b80031e28..802e734bd 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -98,7 +98,16 @@ func contractCreate(ic *interop.Context, v *vm.VM) error { contract, err := ic.DAO.GetContractState(newcontract.ScriptHash()) if contract != nil { return errors.New("contract already exists") - } else if err := ic.DAO.PutContractState(newcontract); err != nil { + } + id, err := ic.DAO.GetNextContractID() + if err != nil { + return err + } + newcontract.ID = id + if err := ic.DAO.PutNextContractID(id); err != nil { + return err + } + if err := ic.DAO.PutContractState(newcontract); err != nil { return err } v.Estack().PushVal(stackitem.NewInterop(newcontract)) diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 960f9ba89..5e70334ed 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -18,6 +18,7 @@ const ( IXHeaderHashList KeyPrefix = 0x80 SYSCurrentBlock KeyPrefix = 0xc0 SYSCurrentHeader KeyPrefix = 0xc1 + SYSContractID KeyPrefix = 0xc2 SYSVersion KeyPrefix = 0xf0 ) From 76a2f62fbd7e4a590c0fc3d672243671afda12b7 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 9 Jun 2020 16:12:58 +0300 Subject: [PATCH 06/10] cli: use manifest during contract deployment --- cli/smartcontract/smart_contract.go | 89 ++++++++++++++------------- pkg/compiler/compiler.go | 4 +- pkg/compiler/debug.go | 16 ++--- pkg/compiler/debug_test.go | 25 +------- pkg/rpc/request/txBuilder.go | 41 +++--------- pkg/smartcontract/deployment_price.go | 18 ------ 6 files changed, 65 insertions(+), 128 deletions(-) delete mode 100644 pkg/smartcontract/deployment_price.go diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index ccf9bb1bf..e22b986ac 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -1,7 +1,6 @@ package smartcontract import ( - "bufio" "bytes" "context" "encoding/hex" @@ -22,6 +21,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -323,23 +323,22 @@ func initSmartContract(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - // Ask contract information and write a neo-go.yml file unless the -skip-details flag is set. - // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. - if !ctx.Bool("skip-details") { - details := parseContractDetails() - details.ReturnType = smartcontract.ByteArrayType - details.Parameters = make([]smartcontract.ParamType, 2) - details.Parameters[0] = smartcontract.StringType - details.Parameters[1] = smartcontract.ArrayType - - project := &ProjectConfig{Contract: details} - b, err := yaml.Marshal(project) - if err != nil { - return cli.NewExitError(err, 1) - } - if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { - return cli.NewExitError(err, 1) - } + m := ProjectConfig{ + EntryPoint: manifest.Method{ + Name: "Main", + Parameters: []manifest.Parameter{ + manifest.NewParameter("Method", smartcontract.StringType), + manifest.NewParameter("Arguments", smartcontract.ArrayType), + }, + ReturnType: smartcontract.ByteArrayType, + }, + } + b, err := yaml.Marshal(m) + if err != nil { + return cli.NewExitError(err, 1) + } + if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { + return cli.NewExitError(err, 1) } data := []byte(fmt.Sprintf(smartContractTmpl, contractName)) @@ -375,7 +374,7 @@ func contractCompile(ctx *cli.Context) error { if err != nil { return err } - o.ContractDetails = &conf.Contract + o.ContractFeatures = conf.GetFeatures() } result, err := compiler.CompileAndSave(src, o) @@ -526,30 +525,35 @@ func testInvokeScript(ctx *cli.Context) error { // ProjectConfig contains project metadata. type ProjectConfig struct { - Version uint - Contract smartcontract.ContractDetails `yaml:"project"` + HasStorage bool + IsPayable bool + EntryPoint manifest.Method + Methods []manifest.Method + Events []manifest.Event } -func parseContractDetails() smartcontract.ContractDetails { - details := smartcontract.ContractDetails{} - reader := bufio.NewReader(os.Stdin) +// GetFeatures returns smartcontract features from the config. +func (p *ProjectConfig) GetFeatures() smartcontract.PropertyState { + var fs smartcontract.PropertyState + if p.IsPayable { + fs |= smartcontract.IsPayable + } + if p.HasStorage { + fs |= smartcontract.HasStorage + } + return fs +} - fmt.Print("Author: ") - details.Author, _ = reader.ReadString('\n') - - fmt.Print("Email: ") - details.Email, _ = reader.ReadString('\n') - - fmt.Print("Version: ") - details.Version, _ = reader.ReadString('\n') - - fmt.Print("Project name: ") - details.ProjectName, _ = reader.ReadString('\n') - - fmt.Print("Description: ") - details.Description, _ = reader.ReadString('\n') - - return details +// ToManifest converts project config to the manifest. +func (p *ProjectConfig) ToManifest(script []byte) *manifest.Manifest { + h := hash.Hash160(script) + m := manifest.NewManifest(h) + m.Features = p.GetFeatures() + m.ABI.Hash = h + m.ABI.EntryPoint = p.EntryPoint + m.ABI.Methods = p.Methods + m.ABI.Events = p.Events + return m } func inspect(ctx *cli.Context) error { @@ -646,13 +650,12 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - txScript, err := request.CreateDeploymentScript(avm, &conf.Contract) + m := conf.ToManifest(avm) + txScript, sysfee, err := request.CreateDeploymentScript(avm, m) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) } - sysfee := smartcontract.GetDeploymentPrice(request.DetailsToSCProperties(&conf.Contract)) - txHash, err := c.SignAndPushInvocationTx(txScript, acc, sysfee, gas) if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index f72d7ba47..59abcacf8 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -32,7 +32,7 @@ type Options struct { ABIInfo string // Contract metadata. - ContractDetails *smartcontract.ContractDetails + ContractFeatures smartcontract.PropertyState } type buildInfo struct { @@ -118,7 +118,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { if o.ABIInfo == "" { return b, err } - abi := di.convertToABI(b, o.ContractDetails) + abi := di.convertToABI(b, o.ContractFeatures) abiData, err := json.Marshal(abi) if err != nil { return b, err diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 9f174b5cc..c6b4b6c69 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -311,14 +311,16 @@ func parsePairJSON(data []byte, sep string) (string, string, error) { return ss[0], ss[1], nil } -func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDetails) ABI { +// convertToABI converts contract to the ABI struct for debugger. +// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038. +func (di *DebugInfo) convertToABI(contract []byte, fs smartcontract.PropertyState) ABI { methods := make([]Method, 0) for _, method := range di.Methods { if method.Name.Name == di.EntryPoint { methods = append(methods, Method{ Name: method.Name.Name, Parameters: method.Parameters, - ReturnType: cd.ReturnType.String(), + ReturnType: method.ReturnType, }) break } @@ -333,14 +335,8 @@ func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDet return ABI{ Hash: hash.Hash160(contract), Metadata: Metadata{ - Author: cd.Author, - Email: cd.Email, - Version: cd.Version, - Title: cd.ProjectName, - Description: cd.Description, - HasStorage: cd.HasStorage, - HasDynamicInvocation: cd.HasDynamicInvocation, - IsPayable: cd.IsPayable, + HasStorage: fs&smartcontract.HasStorage != 0, + IsPayable: fs&smartcontract.IsPayable != 0, }, EntryPoint: di.EntryPoint, Functions: methods, diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 25cd7d035..7059e1384 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -117,33 +117,10 @@ func methodStruct() struct{} { return struct{}{} } } t.Run("convert to ABI", func(t *testing.T) { - author := "Joe" - email := "Joe@ex.com" - version := "1.0" - title := "MyProj" - description := "Description" - actual := d.convertToABI(buf, &smartcontract.ContractDetails{ - Author: author, - Email: email, - Version: version, - ProjectName: title, - Description: description, - HasStorage: true, - HasDynamicInvocation: false, - IsPayable: false, - ReturnType: smartcontract.BoolType, - Parameters: []smartcontract.ParamType{ - smartcontract.StringType, - }, - }) + actual := d.convertToABI(buf, smartcontract.HasStorage) expected := ABI{ Hash: hash.Hash160(buf), Metadata: Metadata{ - Author: author, - Email: email, - Version: version, - Title: title, - Description: description, HasStorage: true, HasDynamicInvocation: false, IsPayable: false, diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index 05def02a3..5c3cd1eed 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -5,49 +5,28 @@ import ( "fmt" "strconv" + "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// DetailsToSCProperties extract the fields needed from ContractDetails -// and converts them to smartcontract.PropertyState. -func DetailsToSCProperties(contract *smartcontract.ContractDetails) smartcontract.PropertyState { - var props smartcontract.PropertyState - if contract.HasStorage { - props |= smartcontract.HasStorage - } - if contract.HasDynamicInvocation { - props |= smartcontract.HasDynamicInvoke - } - if contract.IsPayable { - props |= smartcontract.IsPayable - } - return props -} - // CreateDeploymentScript returns a script that deploys given smart contract -// with its metadata. -func CreateDeploymentScript(avm []byte, contract *smartcontract.ContractDetails) ([]byte, error) { +// with its metadata and system fee require for this. +func CreateDeploymentScript(avm []byte, manif *manifest.Manifest) ([]byte, util.Fixed8, error) { script := io.NewBufBinWriter() - emit.Bytes(script.BinWriter, []byte(contract.Description)) - emit.Bytes(script.BinWriter, []byte(contract.Email)) - emit.Bytes(script.BinWriter, []byte(contract.Author)) - emit.Bytes(script.BinWriter, []byte(contract.Version)) - emit.Bytes(script.BinWriter, []byte(contract.ProjectName)) - emit.Int(script.BinWriter, int64(DetailsToSCProperties(contract))) - emit.Int(script.BinWriter, int64(contract.ReturnType)) - params := make([]byte, len(contract.Parameters)) - for k := range contract.Parameters { - params[k] = byte(contract.Parameters[k]) - } - emit.Bytes(script.BinWriter, params) + w := io.NewBufBinWriter() + manif.EncodeBinary(w.BinWriter) + rawManifest := w.Bytes() + emit.Bytes(script.BinWriter, rawManifest) emit.Bytes(script.BinWriter, avm) emit.Syscall(script.BinWriter, "Neo.Contract.Create") - return script.Bytes(), nil + sysfee := util.Fixed8(core.StoragePrice * (len(avm) + len(rawManifest))) + return script.Bytes(), sysfee, nil } // expandArrayIntoScript pushes all FuncParam parameters from the given array diff --git a/pkg/smartcontract/deployment_price.go b/pkg/smartcontract/deployment_price.go deleted file mode 100644 index 5ba9b8929..000000000 --- a/pkg/smartcontract/deployment_price.go +++ /dev/null @@ -1,18 +0,0 @@ -package smartcontract - -import "github.com/nspcc-dev/neo-go/pkg/util" - -// GetDeploymentPrice returns contract deployment price based on its properties. -func GetDeploymentPrice(props PropertyState) util.Fixed8 { - fee := int64(100) - - if props&HasStorage != 0 { - fee += 400 - } - - if props&HasDynamicInvoke != 0 { - fee += 500 - } - - return util.Fixed8FromInt64(fee) -} From 5514b3f52f77f9727e565ddc0f591af673a005f7 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 9 Jun 2020 16:16:32 +0300 Subject: [PATCH 07/10] smartcontract,vm: remove DynamicInvoke feature It doesn't exist in NEO3. --- pkg/core/interop/context.go | 6 +++--- pkg/core/interop_system.go | 2 +- pkg/core/state/contract.go | 5 ----- pkg/smartcontract/parameter.go | 7 +++---- pkg/vm/context.go | 3 --- pkg/vm/vm.go | 10 ---------- 6 files changed, 7 insertions(+), 26 deletions(-) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 026045baa..0fdd8cb57 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -89,12 +89,12 @@ type ContractMD struct { } // GetContract returns script of the contract with the specified hash. -func (ic *Context) GetContract(h util.Uint160) ([]byte, bool) { +func (ic *Context) GetContract(h util.Uint160) []byte { cs, err := ic.DAO.GetContractState(h) if err != nil { - return nil, false + return nil } - return cs.Script, cs.HasDynamicInvoke() + return cs.Script } // NewContractMD returns Contract with the specified list of methods. diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 2c4f6335e..5e8435ce1 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -484,7 +484,7 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac if err != nil { return errors.New("invalid contract hash") } - script, _ := ic.GetContract(u) + script := ic.GetContract(u) if script == nil { return errors.New("contract not found") } diff --git a/pkg/core/state/contract.go b/pkg/core/state/contract.go index 86e9a231d..227b13ab7 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -53,11 +53,6 @@ func (cs *Contract) HasStorage() bool { return (cs.Manifest.Features & smartcontract.HasStorage) != 0 } -// HasDynamicInvoke checks whether the contract has dynamic invoke property set. -func (cs *Contract) HasDynamicInvoke() bool { - return (cs.Manifest.Features & smartcontract.HasDynamicInvoke) != 0 -} - // IsPayable checks whether the contract has payable property set. func (cs *Contract) IsPayable() bool { return (cs.Manifest.Features & smartcontract.IsPayable) != 0 diff --git a/pkg/smartcontract/parameter.go b/pkg/smartcontract/parameter.go index 303d47af8..4242cb08a 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -22,10 +22,9 @@ type PropertyState byte // List of supported properties. const ( - HasStorage PropertyState = 1 << iota - HasDynamicInvoke - IsPayable - NoProperties = 0 + HasStorage PropertyState = 1 << iota + IsPayable PropertyState = 1 << 2 + NoProperties = 0 ) // Parameter represents a smart contract parameter. diff --git a/pkg/vm/context.go b/pkg/vm/context.go index a32942893..83e385d19 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -39,9 +39,6 @@ type Context struct { // Script hash of the prog. scriptHash util.Uint160 - - // Whether it's allowed to make dynamic calls from this context. - hasDynamicInvoke bool } var errNoInstParam = errors.New("failed to read instruction parameter") diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 3f933595f..7503e5869 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -268,16 +268,6 @@ func (v *VM) LoadScript(b []byte) { v.istack.PushVal(ctx) } -// loadScriptWithHash if similar to the LoadScript method, but it also loads -// given script hash directly into the Context to avoid its recalculations. It's -// up to user of this function to make sure the script and hash match each other. -func (v *VM) loadScriptWithHash(b []byte, hash util.Uint160, hasDynamicInvoke bool) { - v.LoadScript(b) - ctx := v.Context() - ctx.scriptHash = hash - ctx.hasDynamicInvoke = hasDynamicInvoke -} - // Context returns the current executed context. Nil if there is no context, // which implies no program is loaded. func (v *VM) Context() *Context { From c69f8a2fa3b5c47f85bf625abb95959090b85248 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 9 Jun 2020 16:24:03 +0300 Subject: [PATCH 08/10] core: check for permission in System.Contract.Call(Ex) --- pkg/core/interop/context.go | 9 --------- pkg/core/interop_system.go | 17 +++++++++++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 0fdd8cb57..c9c833e99 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -88,15 +88,6 @@ 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 { - cs, err := ic.DAO.GetContractState(h) - if err != nil { - return nil - } - return cs.Script -} - // 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 5e8435ce1..1ac2948d7 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -484,12 +484,21 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac if err != nil { return errors.New("invalid contract hash") } - script := ic.GetContract(u) - if script == nil { + cs, err := ic.DAO.GetContractState(u) + if err != nil { return errors.New("contract not found") } - // TODO perform flags checking after #923 - v.LoadScript(script) + bs, err := method.TryBytes() + if err != nil { + return err + } + curr, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) + if err == nil { + if !curr.Manifest.CanCall(&cs.Manifest, string(bs)) { + return errors.New("disallowed method call") + } + } + v.LoadScript(cs.Script) v.Estack().PushVal(args) v.Estack().PushVal(method) return nil From f8a11f61b62f7fc16887992c80f7df7383b30025 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 10 Jun 2020 14:07:26 +0300 Subject: [PATCH 09/10] core: update *.Contract.* interops 1. Remove GetScript, IsPayable, GetStorageContext. 2. Revert 82319538 related to GetStorageContext. 3. Rename Migrate to Update. 4. Move remaining to System.Contract.*. Related #1031. --- pkg/compiler/syscall.go | 9 +++---- pkg/core/helper_test.go | 2 +- pkg/core/interop/context.go | 2 -- pkg/core/interop_neo.go | 26 ++----------------- pkg/core/interop_neo_test.go | 22 ---------------- pkg/core/interop_system.go | 22 ---------------- pkg/core/interops.go | 9 ++----- pkg/core/interops_test.go | 3 --- pkg/interop/contract/contract.go | 34 +++++-------------------- pkg/rpc/request/txBuilder.go | 2 +- pkg/rpc/server/server_test.go | 6 ++--- pkg/rpc/server/testdata/testblocks.acc | Bin 6784 -> 6784 bytes 12 files changed, 18 insertions(+), 119 deletions(-) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index f4fad18b5..374dacfe7 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -37,12 +37,9 @@ var syscalls = map[string]map[string]string{ "GetTransactionHeight": "System.Blockchain.GetTransactionHeight", }, "contract": { - "GetScript": "Neo.Contract.GetScript", - "IsPayable": "Neo.Contract.IsPayable", - "Create": "Neo.Contract.Create", - "Destroy": "Neo.Contract.Destroy", - "Migrate": "Neo.Contract.Migrate", - "GetStorageContext": "Neo.Contract.GetStorageContext", + "Create": "System.Contract.Create", + "Destroy": "System.Contract.Destroy", + "Update": "System.Contract.Update", }, "engine": { "GetScriptContainer": "System.ExecutionEngine.GetScriptContainer", diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index f8a20f059..3c04e798e 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -236,7 +236,7 @@ func TestCreateBasicChain(t *testing.T) { require.NoError(t, err) emit.Bytes(script.BinWriter, bs) emit.Bytes(script.BinWriter, avm) - emit.Syscall(script.BinWriter, "Neo.Contract.Create") + emit.Syscall(script.BinWriter, "System.Contract.Create") txScript := script.Bytes() invFee := util.Fixed8FromFloat(100) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index c9c833e99..a6ac1f393 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -28,7 +28,6 @@ type Context struct { Block *block.Block Tx *transaction.Transaction DAO *dao.Cached - LowerDAO dao.DAO Notifications []state.NotificationEvent Log *zap.Logger } @@ -44,7 +43,6 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n Block: block, Tx: tx, DAO: dao, - LowerDAO: d, Notifications: nes, Log: log, } diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 802e734bd..9b57250e6 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -114,30 +114,8 @@ func contractCreate(ic *interop.Context, v *vm.VM) error { return nil } -// contractGetScript returns a script associated with a contract. -func contractGetScript(ic *interop.Context, v *vm.VM) error { - csInterface := v.Estack().Pop().Value() - cs, ok := csInterface.(*state.Contract) - if !ok { - return fmt.Errorf("%T is not a contract state", cs) - } - v.Estack().PushVal(cs.Script) - return nil -} - -// contractIsPayable returns whether contract is payable. -func contractIsPayable(ic *interop.Context, v *vm.VM) error { - csInterface := v.Estack().Pop().Value() - cs, ok := csInterface.(*state.Contract) - if !ok { - return fmt.Errorf("%T is not a contract state", cs) - } - v.Estack().PushVal(cs.IsPayable()) - return nil -} - -// contractMigrate migrates a contract. -func contractMigrate(ic *interop.Context, v *vm.VM) error { +// contractUpdate migrates a contract. +func contractUpdate(ic *interop.Context, v *vm.VM) error { contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) if contract == nil { return errors.New("contract doesn't exist") diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 0170cb00e..063ab2775 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -212,28 +212,6 @@ func TestECDSAVerify(t *testing.T) { }) } -func TestContractGetScript(t *testing.T) { - v, contractState, context, chain := createVMAndContractState(t) - defer chain.Close() - v.Estack().PushVal(stackitem.NewInterop(contractState)) - - err := contractGetScript(context, v) - require.NoError(t, err) - script := v.Estack().Pop().Value() - require.Equal(t, contractState.Script, script) -} - -func TestContractIsPayable(t *testing.T) { - v, contractState, context, chain := createVMAndContractState(t) - defer chain.Close() - v.Estack().PushVal(stackitem.NewInterop(contractState)) - - err := contractIsPayable(context, v) - require.NoError(t, err) - isPayable := v.Estack().Pop().Value() - require.Equal(t, contractState.IsPayable(), isPayable) -} - // Helper functions to create VM, InteropContext, TX, Account, Contract. func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) { diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 1ac2948d7..09239598b 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -529,25 +529,3 @@ func contractDestroy(ic *interop.Context, v *vm.VM) error { } return nil } - -// contractGetStorageContext retrieves StorageContext of a contract. -func contractGetStorageContext(ic *interop.Context, v *vm.VM) error { - csInterface := v.Estack().Pop().Value() - cs, ok := csInterface.(*state.Contract) - if !ok { - return fmt.Errorf("%T is not a contract state", cs) - } - _, err := ic.DAO.GetContractState(cs.ScriptHash()) - if err != nil { - return fmt.Errorf("non-existent contract") - } - _, err = ic.LowerDAO.GetContractState(cs.ScriptHash()) - if err == nil { - return fmt.Errorf("contract was not created in this transaction") - } - stc := &StorageContext{ - ScriptHash: cs.ScriptHash(), - } - v.Estack().PushVal(stackitem.NewInterop(stc)) - return nil -} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index c73f48105..819be06ab 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -70,8 +70,9 @@ var systemInterops = []interop.Function{ {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.Create", Func: contractCreate, Price: 0}, {Name: "System.Contract.Destroy", Func: contractDestroy, Price: 1}, - {Name: "System.Contract.GetStorageContext", Func: contractGetStorageContext, Price: 1}, + {Name: "System.Contract.Update", Func: contractUpdate, Price: 0}, {Name: "System.Enumerator.Concat", Func: enumerator.Concat, Price: 1}, {Name: "System.Enumerator.Create", Func: enumerator.Create, Price: 1}, {Name: "System.Enumerator.Next", Func: enumerator.Next, Price: 1}, @@ -104,12 +105,6 @@ var systemInterops = []interop.Function{ } var neoInterops = []interop.Function{ - {Name: "Neo.Contract.Create", Func: contractCreate, Price: 0}, - {Name: "Neo.Contract.Destroy", Func: contractDestroy, Price: 1}, - {Name: "Neo.Contract.GetScript", Func: contractGetScript, Price: 1}, - {Name: "Neo.Contract.GetStorageContext", Func: contractGetStorageContext, Price: 1}, - {Name: "Neo.Contract.IsPayable", Func: contractIsPayable, Price: 1}, - {Name: "Neo.Contract.Migrate", Func: contractMigrate, Price: 0}, {Name: "Neo.Crypto.ECDsaVerify", Func: crypto.ECDSAVerify, Price: 1}, {Name: "Neo.Crypto.ECDsaCheckMultiSig", Func: crypto.ECDSACheckMultisig, Price: 1}, {Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1}, diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go index b346926e2..168edf1a9 100644 --- a/pkg/core/interops_test.go +++ b/pkg/core/interops_test.go @@ -32,9 +32,6 @@ func TestUnexpectedNonInterops(t *testing.T) { // All of these functions expect an interop item on the stack. funcs := []func(*interop.Context, *vm.VM) error{ - contractGetScript, - contractGetStorageContext, - contractIsPayable, storageContextAsReadOnly, storageDelete, storageFind, diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go index 4a67f0748..509b41a17 100644 --- a/pkg/interop/contract/contract.go +++ b/pkg/interop/contract/contract.go @@ -3,54 +3,32 @@ Package contract provides functions to work with contracts. */ package contract -import "github.com/nspcc-dev/neo-go/pkg/interop/storage" - // Contract represents a Neo contract and is used in interop functions. It's // an opaque data structure that you can manipulate with using functions from // this package. It's similar in function to the Contract class in the Neo .net // framework. type Contract struct{} -// GetScript returns the script of the given contract. It uses -// `Neo.Contract.GetScript` syscall. -func GetScript(c Contract) []byte { - return nil -} - -// IsPayable returns whether the given contract is payable (able to accept -// asset transfers to its address). It uses `Neo.Contract.IsPayable` syscall. -func IsPayable(c Contract) bool { - return false -} - -// GetStorageContext returns storage context for the given contract. It only -// works for contracts created in this transaction (so you can't take a storage -// context for arbitrary contract). Refer to the `storage` package on how to -// use this context. This function uses `Neo.Contract.GetStorageContext` syscall. -func GetStorageContext(c Contract) storage.Context { - return storage.Context{} -} - // Create creates a new contract using a set of input parameters: // script contract's bytecode (limited in length by 1M) // manifest contract's manifest (limited in length by 2 KiB) // It returns this new created Contract when successful (and fails transaction -// if not). It uses `Neo.Contract.Create` syscall. +// if not). It uses `System.Contract.Create` syscall. func Create(script []byte, manifest []byte) Contract { return Contract{} } -// Migrate migrates calling contract (that is the one that calls Migrate) to -// the new contract. Its parameters have exactly the same semantics as for +// Update updates script and manifest of the calling contract (that is the one that calls Update) +// to the new ones. Its parameters have exactly the same semantics as for // Create. The old contract will be deleted by this call, if it has any storage // associated it will be migrated to the new contract. New contract is returned. -// This function uses `Neo.Contract.Migrate` syscall. -func Migrate(script []byte, manifest []byte) Contract { +// This function uses `System.Contract.Update` syscall. +func Update(script []byte, manifest []byte) Contract { return Contract{} } // Destroy deletes calling contract (the one that calls Destroy) from the // blockchain, so it's only possible to do that from the contract itself and // not by any outside code. When contract is deleted all associated storage -// items are deleted too. This function uses `Neo.Contract.Destroy` syscall. +// items are deleted too. This function uses `System.Contract.Destroy` syscall. func Destroy() {} diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index 5c3cd1eed..7176ff7f2 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -24,7 +24,7 @@ func CreateDeploymentScript(avm []byte, manif *manifest.Manifest) ([]byte, util. rawManifest := w.Bytes() emit.Bytes(script.BinWriter, rawManifest) emit.Bytes(script.BinWriter, avm) - emit.Syscall(script.BinWriter, "Neo.Contract.Create") + emit.Syscall(script.BinWriter, "System.Contract.Create") sysfee := util.Fixed8(core.StoragePrice * (len(avm) + len(rawManifest))) return script.Bytes(), sysfee, nil } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index bcc28e0f5..2d502f960 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -56,12 +56,12 @@ var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { name: "positive", - params: `["9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7"]`, + params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`, result: func(e *executor) interface{} { return &result.ApplicationLog{} }, check: func(t *testing.T, e *executor, acc interface{}) { res, ok := acc.(*result.ApplicationLog) require.True(t, ok) - expectedTxHash, err := util.Uint256DecodeStringLE("9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7") + expectedTxHash, err := util.Uint256DecodeStringLE("136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20") require.NoError(t, err) assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, 1, len(res.Executions)) @@ -483,7 +483,7 @@ var rpcTestCases = map[string][]rpcTestCase{ "gettransactionheight": { { name: "positive", - params: `["9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7"]`, + params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`, result: func(e *executor) interface{} { h := 0 return &h diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 39cd4d68c1e202b51e21b1a945c199c9f359007d..48261344581038f8c46b38b4dce64a704ccaeeaf 100644 GIT binary patch delta 2015 zcmV<52O#)>HGnmcOar;B=#ft&e^K}SKuYgFPuG+h*+Y0Qfz~QGnXcDdU`FwBbu|on zYLEDjIOajPLyuH=syef|!g3kTH3%c-Xk)UAwwZO=&kR5nfuIv=qq+kSEsE6T35cx= zIbETw4N|ZPCHnD2l>K5Q4*5!S7{8jq$KJ)l6}7j5w|sZHFV~qi zMXl%?sR8^WwTBEqZsk(;Xz2wN4|mNi%WiNEi*iqBrT11BF?Y$+3#RH>-A>->?`T%z z@K9vVc2zDALZJ&2r>2sVf4XP^YKo0Gu}8hx7{V_*Udzj}go1mLQ7h67$D}C|sfK`gnbt99xF5+YR}KV*sxsvB>cuL$ ztmu>E1|xr|N2+JynM(leNy2Lw=7!w2jg)#NiDiB9-unl!IIbwP5g{x1*ohEIw4g+v zLiXKjL&ah3(O#FGhc!4QwH-7JK-t+79TnC}bBSivcdUlVhb#Dl7F_0X9A+qCP-+?` zJg?P^0sdt&l(X|6E0`<2`lrVR4uw3IpA{eGg#Uj%%xVlk&qv-VzFJ9mw1TR;wrh<{ zHoi47W<0^53y{d_Uid{<*;Jfj`pm~^Q{KS)iJBR;#k^T*nSu|A*x z9yErAsJ!0%s(_uvg52vEeP~Vl!cNMikP@l&jbr$j@S%)bi&~BCtz7~{ZIdt-Dk$Ru z00000000?#yJ4>6_X^S@{wZ}AEMd&1?qhkFizpU)IupyPnXQwm4Il%%tmu=?4I_Wx zs8Y#T!m5#}u?L3n!*k8SBWcaPEf6ZH9qq3z7w~^3DDVtGWb$vq;ZVSeS7|m$`*f0h;#xf@#JPW+ zuDi(EANdQ7;lONnbi&OkAeK+)ec8@If~6l@d^UssY%_dzA{1Li3_z+grUh~oKHkS9 zHSEXioj$HxcLp1@&aCfXeSh7*dp#FEaDxW3eQeWPe47i2PFj@ZIYn3^XqE_s2ynX} zKGA-Y!2^|(Z5a!bmJiz?$jZZQrByB+=Iy{pC>i&c5UM#Dfa2{RGXCXxe)c$-laUZ0 z3B0W6opJ#H00fij1|5HqholPJZ)p9@2iB_*@&Pp?#`F`#F2Tao2Foq+1$z;#IR4DK*|q}z-bLK&$yFe<*?E1MMN!W={|!K zwP6BOr{-t%=oZ@cC)Dq~dHv#lRAw*dUUQ{<1#fKLR=llzUv+r7 zC6mqnc=Yw8|DgIt2(>3Cu+d|gSXzq5Ymd>@vj0)zFgI|s&`oBnr2Wu52;7-^cFfuI zNH?xW3_wmV$%O3S8@&h}?ry@npkPXfX8w#rxnz+Tdt0v*FWQyLRp=r>b#z(dw%%~I zgjDoRF!Bh#{KCPi)>4g=!2^|(Z5a!*eG(A?Ae!q#T@!r0+RUrsB$W!3APTr$ zoINu@#i5r$ngX7ql#{L#AOpRu=#$YCBY&+^b^8^&fK`72**V2}D3f>80WzlBQ_V4C zK6k+>v0wlw$}>c&#T<|XZ^Hqw_!Tz`acE8M%Xb`Rg+;cBZ~+WJ4w02UJlb6iUvRkW z0vV)=&x&x6>Ms6nLaXZYET$GN4($7$_UB;>77-KxngFG+!&%Z47=uWY{AKsqyMLKB zH4H!)`@}B;_5Zs;7LSjFUxR}TKyK6t-8$5GxB-w=Gopu5IkFXQdjbpWUu?({Vc8H5lnB}N z=A-1wRC*x=KJ$8XM^;ejq0oa#MF`pDSKnKSYtoa!1C_In72N?KlsO(LiWuIDKuERk zQjHH47NP>Z+Gz!q3mY4B`To58lZ+Q21HP>2ldl&ee_s^qz=xr8Es4yy!EI>H|65Fq z7=QTd=Z3@Y<$rS@L@!6nL|w++{NeLa9&2sPP_WwkI7kP}@N{(1E==H`!`T3)BkQLLqgh?6x=MbyW-tmiE zTC&HMf5vv)3_wrOZiD^f2G>Y1a)!z9#GB{M1m;TrNN-}OW4dx}TeVIRCQ#3CTN5}t zxK~<}NFKI&7WyE7u70u1k5$*^h&c>EmQd`Ic1_4R%Y<-`#*0(N^}B}(o`)yITKi|R xD?IYvskcWjWTOauFv9wSs|jXkI&b>mP&`9+2()4tH|?Cbc$2{cm9uRb69J}_)ZqXC delta 2015 zcmV<52O#)>HGnmcOarN-|dX|YPx~2)kRH;zcvZ>jp`x zqvMn01|xss-ay=y!DWAC1$O#Z-pVh7(4690e7BM5&2c$Tc+~p6Nx?!VM>hwtT`$N7t;sY(lu1qewaoK;T2m7s#;tiU9v_K}eJRWJ1bpgixKOt*`=j^9oS> z)4#w|Y=xFnp}K@aL^pGPBAI_NNwZ_Ls#tdmLpg{FJG9O5<$+n>1J<|TsyQ97@=v7~{b~gt+$=b`LFkxFrjsxhDk$Ru z00000003zMLzqWn#pPl+B^Sbf?Zh}$c-1j>ia>TH47B8)$rh8U4Il%nqvMm!4I_Uy zNCBg6|7XhGo2UD`aR_rXL1ho1l(VbqyV#YV8y2AY3dQ$2-}bLJpIilLZt4t7?D5!< zr{K@of915t#v8y4K(98LyFP>EO^%)AK%3Leuh@?g8YB+jNFZ_Nw0MuK#z~+V0eoxI znKVM(KqR)3UY8vxo`UA|y7L!jWvG8Ec0UY2VO0FaAj`UV`0UACP%$*L;B3a>uNgQm zk_G&X$uNwtcdjzLC;I)wu0l|rf&qG2DZ9~y?!FD`qmpox9Bs(p3_y31QAK+2hK$*u zG0mpIV}dKR0Y;@1A*O^BQ}cXHy7c;;$WgCn!M+y6x&;q@&(MPU*ImrNX$=ThbFA!- zy1uBB!2^|(Z5a!bmJiz?gVHKuM-&AjeVpMq_f)bnPUv|dc^|FEj_JjqS4_T6laUZ0 z39O^zopJ#H00fij1|5G(@M?!mLP)kKZ=vEj4MAsq3O~7UhC!q%dni9wZ?Dd88U$-^2)HS_d7Ykv4y+avBUkK=fsxj~bDS z93jHym1UM*YYue}gP;Seyj3@IZ%;^bK|K*lsYh#J zSXU#V3_z^pupXJ02cZi?QRV)zK%bd~5qzj+U$1`Rz{EbQO-e8oFM35Q%48cB@Joo- zky)DY;;&$l?hgp%smEj^?$}b3!2^|(Z5a!*eG(A?AT`xLOV;#fA36ZE^NB1&rU1nP z87L0OuJXRf0shgW0h6v1AOo$V z){Ww*DJm0JgXTzH>Uwp8p3JQ&_T4Y3UqUYA;wllZ+Q21Foavldl&ef6IL$J@~m?>ErQ-@Z{dBFl!O9 zb+7fdyW`3z548PDtW)*hn8@i8QB)Y+!VeLqerBOx`)jN6Ph7pFP)5K^lng+Gk@{Jc z2rAiM&qoP*)B^V@!6w3L73MGvy;3~O0dV2ULfcJ`6xWpqKy^ZYuRg2?q;50}E)1TF zL-dZvf8f%>3_#HIa@f11DnjJT%W}T_$z{A&v5-$m1?v>MNb)_qQHTT2o7{SXw_un@ zRDN0rCndhpdWW2|+r3n3wn%WR2ssQuq@)%32THa5nfDOG3ep3qH=)ZW4DltU8|m(2 xS6ASMHYmo2*0r;&fY*NZ&aUq6NBw7Iy>(7_2-m^#Q3!uwzLUWNm9uRb69L|S)Q$iE From a86e1910e14583fd45a324a2ef0eb12190aec633 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 10 Jun 2020 14:36:17 +0300 Subject: [PATCH 10/10] core: remove unused interops Missed in #1040. --- pkg/core/interop_system.go | 56 -------------------------------------- 1 file changed, 56 deletions(-) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 09239598b..285060312 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -179,62 +179,6 @@ func bcGetTransactionHeight(ic *interop.Context, v *vm.VM) error { return nil } -// popHeaderFromVM returns pointer to Header or error. It's main feature is -// proper treatment of Block structure, because C# code implicitly assumes -// that header APIs can also operate on blocks. -func popHeaderFromVM(v *vm.VM) (*block.Header, error) { - iface := v.Estack().Pop().Value() - header, ok := iface.(*block.Header) - if !ok { - block, ok := iface.(*block.Block) - if !ok { - return nil, errors.New("value is not a header or block") - } - return block.Header(), nil - } - return header, nil -} - -// headerGetIndex returns block index from the header. -func headerGetIndex(ic *interop.Context, v *vm.VM) error { - header, err := popHeaderFromVM(v) - if err != nil { - return err - } - v.Estack().PushVal(header.Index) - return nil -} - -// headerGetHash returns header hash of the passed header. -func headerGetHash(ic *interop.Context, v *vm.VM) error { - header, err := popHeaderFromVM(v) - if err != nil { - return err - } - v.Estack().PushVal(header.Hash().BytesBE()) - return nil -} - -// headerGetPrevHash returns previous header hash of the passed header. -func headerGetPrevHash(ic *interop.Context, v *vm.VM) error { - header, err := popHeaderFromVM(v) - if err != nil { - return err - } - v.Estack().PushVal(header.PrevHash.BytesBE()) - return nil -} - -// headerGetTimestamp returns timestamp of the passed header. -func headerGetTimestamp(ic *interop.Context, v *vm.VM) error { - header, err := popHeaderFromVM(v) - if err != nil { - return err - } - v.Estack().PushVal(header.Timestamp) - return nil -} - // engineGetScriptContainer returns transaction that contains the script being // run. func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error {