From 849385a533b3197a7ae12d38ff5c7c49c5691d56 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Thu, 28 Jan 2021 15:36:37 +0300 Subject: [PATCH 1/4] native: allow to overload native methods --- pkg/core/interop/context.go | 26 +++++++++++++++++++++++--- pkg/core/native/interop.go | 2 +- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index c649d6984..83319f08b 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -104,7 +104,13 @@ type ContractMD struct { ContractID int32 NEF nef.File Hash util.Uint160 - Methods map[string]MethodAndPrice + Methods map[MethodAndArgCount]MethodAndPrice +} + +// MethodAndArgCount represents method's signature. +type MethodAndArgCount struct { + Name string + ArgCount int } // NewContractMD returns Contract with the specified list of methods. @@ -112,7 +118,7 @@ func NewContractMD(name string, id int32) *ContractMD { c := &ContractMD{ Name: name, ContractID: id, - Methods: make(map[string]MethodAndPrice), + Methods: make(map[MethodAndArgCount]MethodAndPrice), } // NEF is now stored in contract state and affects state dump. @@ -132,7 +138,21 @@ func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method) { c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, *desc) md.MD = desc desc.Safe = md.RequiredFlags&(callflag.All^callflag.ReadOnly) == 0 - c.Methods[desc.Name] = *md + key := MethodAndArgCount{ + Name: desc.Name, + ArgCount: len(desc.Parameters), + } + c.Methods[key] = *md +} + +// GetMethod returns method `name` with specified number of parameters. +func (c *ContractMD) GetMethod(name string, paramCount int) (MethodAndPrice, bool) { + key := MethodAndArgCount{ + Name: name, + ArgCount: paramCount, + } + mp, ok := c.Methods[key] + return mp, ok } // AddEvent adds new event to a native contract. diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index a9af939f2..c4fb5744d 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -28,7 +28,7 @@ func Call(ic *interop.Context) error { return errors.New("it is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used") } operation := ic.VM.Estack().Pop().String() - m, ok := c.Metadata().Methods[operation] + m, ok := c.Metadata().GetMethod(operation, ic.VM.Estack().Len()) if !ok { return fmt.Errorf("method %s not found", operation) } From c1cc7e6f9df73c7a7a3068c91ceb51ed65cd29a8 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Thu, 28 Jan 2021 16:31:50 +0300 Subject: [PATCH 2/4] native: add additional parameters to deploy 1. Management contract has 2 overloads of `deploy` method. 2. Normal contracts should have `_deploy` with 2 parameters. --- cli/testdata/deploy/main.go | 2 +- cli/testdata/deploy/sub/put.go | 2 +- examples/runtime/runtime.go | 2 +- examples/timer/timer.go | 2 +- pkg/compiler/codegen.go | 6 +-- pkg/compiler/debug.go | 17 +++++--- pkg/compiler/debug_test.go | 20 +++++++--- pkg/core/interop_system_test.go | 28 +++++++++++-- pkg/core/native/management.go | 39 ++++++++++++++---- pkg/core/native_management_test.go | 63 +++++++++++++++++++++++++++++- 10 files changed, 149 insertions(+), 32 deletions(-) diff --git a/cli/testdata/deploy/main.go b/cli/testdata/deploy/main.go index 514c3e448..8a181dc77 100644 --- a/cli/testdata/deploy/main.go +++ b/cli/testdata/deploy/main.go @@ -13,7 +13,7 @@ var key = "key" const mgmtKey = "mgmt" -func _deploy(isUpdate bool) { +func _deploy(data interface{}, isUpdate bool) { var value string ctx := storage.GetContext() diff --git a/cli/testdata/deploy/sub/put.go b/cli/testdata/deploy/sub/put.go index c3fe1c6ea..e21b3cbcf 100644 --- a/cli/testdata/deploy/sub/put.go +++ b/cli/testdata/deploy/sub/put.go @@ -4,7 +4,7 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/storage" var Key = "sub" -func _deploy(isUpdate bool) { +func _deploy(data interface{}, isUpdate bool) { ctx := storage.GetContext() value := "sub create" if isUpdate { diff --git a/examples/runtime/runtime.go b/examples/runtime/runtime.go index 27de617fd..aa0d04255 100644 --- a/examples/runtime/runtime.go +++ b/examples/runtime/runtime.go @@ -16,7 +16,7 @@ func init() { trigger = runtime.GetTrigger() } -func _deploy(isUpdate bool) { +func _deploy(_ interface{}, isUpdate bool) { if isUpdate { Log("_deploy method called before contract update") return diff --git a/examples/timer/timer.go b/examples/timer/timer.go index 91f52a3ef..a38d90ce1 100644 --- a/examples/timer/timer.go +++ b/examples/timer/timer.go @@ -25,7 +25,7 @@ func init() { ctx = storage.GetContext() } -func _deploy(isUpdate bool) { +func _deploy(_ interface{}, isUpdate bool) { if isUpdate { ticksLeft := storage.Get(ctx, ticksKey).(int) + 1 storage.Put(ctx, ticksKey, ticksLeft) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 138513962..b9204edff 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -336,11 +336,11 @@ func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package, seenBefore b func isDeployFunc(decl *ast.FuncDecl) bool { if decl.Name.Name != "_deploy" || decl.Recv != nil || - decl.Type.Params.NumFields() != 1 || + decl.Type.Params.NumFields() != 2 || decl.Type.Results.NumFields() != 0 { return false } - typ, ok := decl.Type.Params.List[0].Type.(*ast.Ident) + typ, ok := decl.Type.Params.List[1].Type.(*ast.Ident) return ok && typ.Name == "bool" } @@ -1869,7 +1869,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { hasDeploy := deployLocals > -1 if hasDeploy { - emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(deployLocals), 1}) + emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(deployLocals), 2}) c.convertDeployFuncs() c.deployEndOffset = c.prog.Len() emit.Opcodes(c.prog.BinWriter, opcode.RET) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index c21a6b140..e91acd718 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -150,11 +150,18 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo { Start: uint16(c.initEndOffset + 1), End: uint16(c.deployEndOffset), }, - Parameters: []DebugParam{{ - Name: "isUpdate", - Type: "Boolean", - TypeSC: smartcontract.BoolType, - }}, + Parameters: []DebugParam{ + { + Name: "data", + Type: "Any", + TypeSC: smartcontract.AnyType, + }, + { + Name: "isUpdate", + Type: "Boolean", + TypeSC: smartcontract.BoolType, + }, + }, ReturnType: "Void", ReturnTypeSC: smartcontract.VoidType, SeqPoints: c.sequencePoints[manifest.MethodDeploy], diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 506142f6d..ab2ba254a 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -53,7 +53,7 @@ func MethodParams(addr interop.Hash160, h interop.Hash256, type MyStruct struct {} func (ms MyStruct) MethodOnStruct() { } func (ms *MyStruct) MethodOnPointerToStruct() { } -func _deploy(isUpdate bool) {} +func _deploy(data interface{}, isUpdate bool) {} ` info, err := getBuildInfo("foo.go", src) @@ -100,11 +100,18 @@ func _deploy(isUpdate bool) {} t.Run("param types", func(t *testing.T) { paramTypes := map[string][]DebugParam{ - "_deploy": {{ - Name: "isUpdate", - Type: "Boolean", - TypeSC: smartcontract.BoolType, - }}, + "_deploy": { + { + Name: "data", + Type: "Any", + TypeSC: smartcontract.AnyType, + }, + { + Name: "isUpdate", + Type: "Boolean", + TypeSC: smartcontract.BoolType, + }, + }, "MethodInt": {{ Name: "a", Type: "ByteString", @@ -160,6 +167,7 @@ func _deploy(isUpdate bool) {} Name: "_deploy", Offset: 0, Parameters: []manifest.Parameter{ + manifest.NewParameter("data", smartcontract.AnyType), manifest.NewParameter("isUpdate", smartcontract.BoolType), }, ReturnType: smartcontract.VoidType, diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 88ce4fee1..9b574242e 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -479,10 +479,16 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB, opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET) deployOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.JMPIF, 2+8+3) - emit.String(w.BinWriter, "create") - emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+3, opcode.RET) - emit.String(w.BinWriter, "update") + emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+5+3) + emit.String(w.BinWriter, "create") // 8 bytes + emit.Int(w.BinWriter, 2) // 1 byte + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte + emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) // 5 bytes + emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+5+3, opcode.RET) + emit.String(w.BinWriter, "update") // 8 bytes + emit.Int(w.BinWriter, 2) // 1 byte + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte + emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) // 5 bytes emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET) putValOff := w.Len() emit.String(w.BinWriter, "initial") @@ -501,6 +507,9 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { emit.String(w.BinWriter, "LastPayment") emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) emit.Opcodes(w.BinWriter, opcode.RET) + update3Off := w.Len() + emit.Int(w.BinWriter, 3) + emit.Opcodes(w.BinWriter, opcode.JMP, 2+1) updateOff := w.Len() emit.Int(w.BinWriter, 2) emit.Opcodes(w.BinWriter, opcode.PACK) @@ -588,6 +597,7 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { Name: manifest.MethodDeploy, Offset: deployOff, Parameters: []manifest.Parameter{ + manifest.NewParameter("data", smartcontract.AnyType), manifest.NewParameter("isUpdate", smartcontract.BoolType), }, ReturnType: smartcontract.VoidType, @@ -624,6 +634,16 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { }, ReturnType: smartcontract.VoidType, }, + { + Name: "update", + Offset: update3Off, + Parameters: []manifest.Parameter{ + manifest.NewParameter("nef", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType), + manifest.NewParameter("data", smartcontract.AnyType), + }, + ReturnType: smartcontract.VoidType, + }, { Name: "destroy", Offset: destroyOff, diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 76737d551..de167ab82 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -77,12 +77,26 @@ func newManagement() *Management { md = newMethodAndPrice(m.deploy, 0, callflag.WriteStates|callflag.AllowNotify) m.AddMethod(md, desc) + desc = newDescriptor("deploy", smartcontract.ArrayType, + manifest.NewParameter("script", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType), + manifest.NewParameter("data", smartcontract.AnyType)) + md = newMethodAndPrice(m.deployWithData, 0, callflag.WriteStates|callflag.AllowNotify) + m.AddMethod(md, desc) + desc = newDescriptor("update", smartcontract.VoidType, manifest.NewParameter("script", smartcontract.ByteArrayType), manifest.NewParameter("manifest", smartcontract.ByteArrayType)) md = newMethodAndPrice(m.update, 0, callflag.WriteStates|callflag.AllowNotify) m.AddMethod(md, desc) + desc = newDescriptor("update", smartcontract.VoidType, + manifest.NewParameter("script", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType), + manifest.NewParameter("data", smartcontract.AnyType)) + md = newMethodAndPrice(m.updateWithData, 0, callflag.WriteStates|callflag.AllowNotify) + m.AddMethod(md, desc) + desc = newDescriptor("destroy", smartcontract.VoidType) md = newMethodAndPrice(m.destroy, 1000000, callflag.WriteStates|callflag.AllowNotify) m.AddMethod(md, desc) @@ -204,9 +218,14 @@ func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stac return resNef, resManifest, nil } -// deploy is an implementation of public deploy method, it's run under -// VM protections, so it's OK for it to panic instead of returning errors. +// deploy is an implementation of public 2-argument deploy method. func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item { + return m.deployWithData(ic, append(args, stackitem.Null{})) +} + +// deployWithData is an implementation of public 3-argument deploy method. +// It's run under VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item { neff, manif, err := m.getNefAndManifestFromItems(ic, args, true) if err != nil { panic(err) @@ -224,7 +243,7 @@ func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackite if err != nil { panic(err) } - m.callDeploy(ic, newcontract, false) + m.callDeploy(ic, newcontract, args[2], false) m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash) return contractToStack(newcontract) } @@ -266,9 +285,13 @@ func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, mani return newcontract, nil } +func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item { + return m.updateWithData(ic, append(args, stackitem.Null{})) +} + // update is an implementation of public update method, it's run under // VM protections, so it's OK for it to panic instead of returning errors. -func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item { neff, manif, err := m.getNefAndManifestFromItems(ic, args, false) if err != nil { panic(err) @@ -280,7 +303,7 @@ func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackite if err != nil { panic(err) } - m.callDeploy(ic, contract, true) + m.callDeploy(ic, contract, args[2], true) m.emitNotification(ic, contractUpdateNotificationName, contract.Hash) return stackitem.Null{} } @@ -378,11 +401,11 @@ func (m *Management) setMinimumDeploymentFee(ic *interop.Context, args []stackit return stackitem.Null{} } -func (m *Management) callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) { - md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy, 1) +func (m *Management) callDeploy(ic *interop.Context, cs *state.Contract, data stackitem.Item, isUpdate bool) { + md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy, 2) if md != nil { err := contract.CallFromNative(ic, m.Hash, cs, manifest.MethodDeploy, - []stackitem.Item{stackitem.NewBool(isUpdate)}, false) + []stackitem.Item{data, stackitem.NewBool(isUpdate)}, false) if err != nil { panic(err) } diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go index 2b8dd5ec0..359bd4c2f 100644 --- a/pkg/core/native_management_test.go +++ b/pkg/core/native_management_test.go @@ -71,6 +71,57 @@ func TestStartFromHeight(t *testing.T) { checkContractState(t, bc2, cs1.Hash, cs1) } +func TestContractDeployAndUpdateWithParameter(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + // nef.NewFile() cares about version a lot. + config.Version = "0.90.0-test" + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} + cs1.ID = 1 + cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Checksum, cs1.Manifest.Name) + manif1, err := json.Marshal(cs1.Manifest) + require.NoError(t, err) + nef1b, err := cs1.NEF.Bytes() + require.NoError(t, err) + + aer, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1, int64(42)) + require.NoError(t, err) + require.Equal(t, vm.HaltState, aer.VMState) + + t.Run("_deploy called", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte)) + require.NoError(t, err) + expected := []stackitem.Item{stackitem.Make("create"), stackitem.Make(42)} + require.Equal(t, stackitem.NewArray(expected), item) + }) + + cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET)) + cs1.NEF.Checksum = cs1.NEF.CalculateChecksum() + nef1b, err = cs1.NEF.Bytes() + require.NoError(t, err) + cs1.UpdateCounter++ + + aer, err = invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, nil, "new data") + require.NoError(t, err) + require.Equal(t, vm.HaltState, aer.VMState) + + t.Run("_deploy called", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte)) + require.NoError(t, err) + expected := []stackitem.Item{stackitem.Make("update"), stackitem.Make("new data")} + require.Equal(t, stackitem.NewArray(expected), item) + }) +} + func TestContractDeploy(t *testing.T) { bc := newTestChain(t) defer bc.Close() @@ -166,7 +217,10 @@ func TestContractDeploy(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") require.NoError(t, err) require.Equal(t, 1, len(res.Stack)) - require.Equal(t, []byte("create"), res.Stack[0].Value()) + item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte)) + require.NoError(t, err) + expected := []stackitem.Item{stackitem.Make("create"), stackitem.Null{}} + require.Equal(t, stackitem.NewArray(expected), item) }) t.Run("get after deploy", func(t *testing.T) { checkContractState(t, bc, cs1.Hash, cs1) @@ -198,6 +252,7 @@ func TestContractDeploy(t *testing.T) { Name: manifest.MethodDeploy, Offset: 0, Parameters: []manifest.Parameter{ + manifest.NewParameter("data", smartcontract.AnyType), manifest.NewParameter("isUpdate", smartcontract.BoolType), }, ReturnType: smartcontract.VoidType, @@ -226,6 +281,7 @@ func TestContractDeploy(t *testing.T) { Name: manifest.MethodDeploy, Offset: 0, Parameters: []manifest.Parameter{ + manifest.NewParameter("data", smartcontract.AnyType), manifest.NewParameter("isUpdate", smartcontract.BoolType), }, ReturnType: smartcontract.ArrayType, @@ -346,7 +402,10 @@ func TestContractUpdate(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") require.NoError(t, err) require.Equal(t, 1, len(res.Stack)) - require.Equal(t, []byte("update"), res.Stack[0].Value()) + item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte)) + require.NoError(t, err) + expected := []stackitem.Item{stackitem.Make("update"), stackitem.Null{}} + require.Equal(t, stackitem.NewArray(expected), item) }) t.Run("check contract", func(t *testing.T) { checkContractState(t, bc, cs1.Hash, cs1) From 2336415f42fadf0404287c0db88f54e8278aedd4 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Thu, 28 Jan 2021 16:43:41 +0300 Subject: [PATCH 3/4] naitve: disallow contract name modification --- pkg/core/native/management.go | 3 +++ pkg/core/native_management_test.go | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index de167ab82..083829570 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -322,6 +322,9 @@ func (m *Management) Update(d dao.DAO, hash util.Uint160, neff *nef.File, manif } // if manifest was provided, update the contract manifest if manif != nil { + if manif.Name != contract.Manifest.Name { + return nil, errors.New("contract name can't be changed") + } if !manif.IsValid(contract.Hash) { return nil, errors.New("invalid manifest for this contract") } diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go index 359bd4c2f..11c2742fa 100644 --- a/pkg/core/native_management_test.go +++ b/pkg/core/native_management_test.go @@ -375,6 +375,17 @@ func TestContractUpdate(t *testing.T) { checkFAULTState(t, res) }) + t.Run("change name", func(t *testing.T) { + var badManifest = cs1.Manifest + badManifest.Name += "tail" + manifB, err := json.Marshal(badManifest) + require.NoError(t, err) + + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manifB) + require.NoError(t, err) + checkFAULTState(t, res) + }) + cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET)) cs1.NEF.Checksum = cs1.NEF.CalculateChecksum() nef1b, err = cs1.NEF.Bytes() From c8a07be58dfd98083d61a210f7a70a599fc8cde2 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Thu, 28 Jan 2021 16:55:03 +0300 Subject: [PATCH 4/4] native: sort methods in manifest --- pkg/core/interop/context.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 83319f08b..5cd231150 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -135,9 +135,20 @@ func NewContractMD(name string, id int32) *ContractMD { // AddMethod adds new method to a native contract. func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method) { - c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, *desc) md.MD = desc desc.Safe = md.RequiredFlags&(callflag.All^callflag.ReadOnly) == 0 + + index := sort.Search(len(c.Manifest.ABI.Methods), func(i int) bool { + md := c.Manifest.ABI.Methods[i] + if md.Name != desc.Name { + return md.Name >= desc.Name + } + return len(md.Parameters) > len(desc.Parameters) + }) + c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, manifest.Method{}) + copy(c.Manifest.ABI.Methods[index+1:], c.Manifest.ABI.Methods[index:]) + c.Manifest.ABI.Methods[index] = *desc + key := MethodAndArgCount{ Name: desc.Name, ArgCount: len(desc.Parameters),