From 34d2eaf00efc428c9672406f8ebcdf122e6dbab6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 17 Nov 2020 22:10:40 +0300 Subject: [PATCH 01/11] manifest: update maximum possible length As per neo-project/neo#2002. --- pkg/smartcontract/manifest/manifest.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index f4f8ad636..8b813bd2e 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -2,6 +2,7 @@ package manifest import ( "encoding/json" + "math" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" @@ -9,7 +10,7 @@ import ( const ( // MaxManifestSize is a max length for a valid contract manifest. - MaxManifestSize = 4096 + MaxManifestSize = math.MaxUint16 // MethodInit is a name for default initialization method. MethodInit = "_initialize" From b92ea2a48a61b17f295efaa525a8a993586ddd7f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 18 Nov 2020 12:43:51 +0300 Subject: [PATCH 02/11] manifest/compiler: drop hashes from ABI and debug info See neo-project/neo-devpack-dotnet#391 and neo-project/neo#2044. --- pkg/compiler/debug.go | 7 +-- pkg/compiler/debug_test.go | 8 --- pkg/core/dao/cacheddao_test.go | 2 +- pkg/core/interop/callback/method.go | 2 +- pkg/core/interop/context.go | 2 +- pkg/core/interop/contract/call.go | 2 +- pkg/core/interop_neo.go | 1 - pkg/core/interop_neo_test.go | 3 +- pkg/core/interop_system_test.go | 45 +++------------- pkg/core/native_oracle_test.go | 2 +- pkg/core/state/contract_test.go | 2 +- pkg/rpc/client/rpc_test.go | 13 +++-- pkg/smartcontract/manifest/manifest.go | 24 ++++----- pkg/smartcontract/manifest/manifest_test.go | 52 +++++++++---------- pkg/smartcontract/manifest/permission.go | 4 +- .../manifest/standard/comply_test.go | 3 +- 16 files changed, 57 insertions(+), 115 deletions(-) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 88e6bcc9a..42cdf88c3 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -11,16 +11,13 @@ import ( "unicode" "unicode/utf8" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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" ) // DebugInfo represents smart-contract debug information. type DebugInfo struct { MainPkg string `json:"-"` - Hash util.Uint160 `json:"hash"` Documents []string `json:"documents"` Methods []MethodDebugInfo `json:"methods"` Events []EventDebugInfo `json:"events"` @@ -115,7 +112,6 @@ func (c *codegen) saveSequencePoint(n ast.Node) { func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo { d := &DebugInfo{ MainPkg: c.mainPkg.Pkg.Name(), - Hash: hash.Hash160(contract), Events: []EventDebugInfo{}, Documents: c.documents, } @@ -427,7 +423,7 @@ func (di *DebugInfo) ConvertToManifest(name string, events []manifest.Event, sup } } - result := manifest.NewManifest(di.Hash, name) + result := manifest.NewManifest(name) if supportedStandards != nil { result.SupportedStandards = supportedStandards } @@ -435,7 +431,6 @@ func (di *DebugInfo) ConvertToManifest(name string, events []manifest.Event, sup events = make([]manifest.Event, 0) } result.ABI = manifest.ABI{ - Hash: di.Hash, Methods: methods, Events: events, } diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 2993f3854..858af186c 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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" @@ -69,10 +68,6 @@ func _deploy(isUpdate bool) {} d := c.emitDebugInfo(buf) require.NotNil(t, d) - t.Run("hash", func(t *testing.T) { - require.True(t, hash.Hash160(buf).Equals(d.Hash)) - }) - t.Run("return types", func(t *testing.T) { returnTypes := map[string]string{ "MethodInt": "Integer", @@ -155,7 +150,6 @@ func _deploy(isUpdate bool) {} expected := &manifest.Manifest{ Name: "MyCTR", ABI: manifest.ABI{ - Hash: hash.Hash160(buf), Methods: []manifest.Method{ { Name: "_deploy", @@ -262,7 +256,6 @@ func _deploy(isUpdate bool) {} }, Extra: nil, } - require.True(t, expected.ABI.Hash.Equals(actual.ABI.Hash)) require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods) require.Equal(t, expected.ABI.Events, actual.ABI.Events) require.Equal(t, expected.Groups, actual.Groups) @@ -304,7 +297,6 @@ func TestSequencePoints(t *testing.T) { func TestDebugInfo_MarshalJSON(t *testing.T) { d := &DebugInfo{ - Hash: util.Uint160{10, 11, 12, 13}, Documents: []string{"/path/to/file"}, Methods: []MethodDebugInfo{ { diff --git a/pkg/core/dao/cacheddao_test.go b/pkg/core/dao/cacheddao_test.go index 190590f15..d54f2ea89 100644 --- a/pkg/core/dao/cacheddao_test.go +++ b/pkg/core/dao/cacheddao_test.go @@ -23,7 +23,7 @@ func TestCachedDaoContracts(t *testing.T) { _, err := dao.GetContractState(sh) require.NotNil(t, err) - m := manifest.NewManifest(hash.Hash160(script), "Test") + m := manifest.NewManifest("Test") cs := &state.Contract{ ID: 123, diff --git a/pkg/core/interop/callback/method.go b/pkg/core/interop/callback/method.go index d595a5a37..78947c3a1 100644 --- a/pkg/core/interop/callback/method.go +++ b/pkg/core/interop/callback/method.go @@ -48,7 +48,7 @@ func CreateFromMethod(ic *interop.Context) error { return errors.New("invalid method name") } currCs, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) - if err == nil && !currCs.Manifest.CanCall(&cs.Manifest, method) { + if err == nil && !currCs.Manifest.CanCall(h, &cs.Manifest, method) { return errors.New("method call is not allowed") } md := cs.Manifest.ABI.GetMethod(method) diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index df4075889..b388e5d22 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -114,7 +114,7 @@ func NewContractMD(name string) *ContractMD { c.Script = w.Bytes() c.Hash = hash.Hash160(c.Script) - c.Manifest = *manifest.DefaultManifest(c.Hash, name) + c.Manifest = *manifest.DefaultManifest(name) return c } diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index d40878e6e..b8f7a8bd0 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -48,7 +48,7 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem } curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) if err == nil { - if !curr.Manifest.CanCall(&cs.Manifest, name) { + if !curr.Manifest.CanCall(u, &cs.Manifest, name) { return errors.New("disallowed method call") } } diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 4e143756a..446ebfbcc 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -162,7 +162,6 @@ func contractUpdate(ic *interop.Context) error { Script: script, Manifest: contract.Manifest, } - contract.Manifest.ABI.Hash = newHash if err := ic.DAO.PutContractState(contract); err != nil { return fmt.Errorf("failed to update script: %w", err) } diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index e17ae875a..d89c9485e 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -16,7 +16,6 @@ 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/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" @@ -294,7 +293,7 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) { script := []byte("testscript") - m := manifest.NewManifest(hash.Hash160(script), "Test") + m := manifest.NewManifest("Test") contractState := &state.Contract{ Script: script, Manifest: *m, diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 42802165b..dc33fb969 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -418,7 +418,7 @@ func getTestContractState() (*state.Contract, *state.Contract) { script := w.Bytes() h := hash.Hash160(script) - m := manifest.NewManifest(h, "TestMain") + m := manifest.NewManifest("TestMain") m.ABI.Methods = []manifest.Method{ { Name: "add", @@ -507,7 +507,7 @@ func getTestContractState() (*state.Contract, *state.Contract) { } currScript := []byte{byte(opcode.RET)} - m = manifest.NewManifest(hash.Hash160(currScript), "TestAux") + m = manifest.NewManifest("TestAux") perm := manifest.NewPermission(manifest.PermissionHash, h) perm.Methods.Add("add") perm.Methods.Add("drop") @@ -551,7 +551,7 @@ func TestContractCall(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(currCs)) currScript := currCs.Script - h := cs.Manifest.ABI.Hash + h := hash.Hash160(cs.Script) addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)}) t.Run("Good", func(t *testing.T) { @@ -669,13 +669,6 @@ func TestContractCreate(t *testing.T) { compareContractStates(t, cs, actual) }) - t.Run("invalid scripthash", func(t *testing.T) { - cs.Script = append(cs.Script, 0x01) - putArgsOnStack() - - require.Error(t, contractCreate(ic)) - }) - t.Run("contract already exists", func(t *testing.T) { cs.Script = cs.Script[:len(cs.Script)-1] require.NoError(t, ic.DAO.PutContractState(cs)) @@ -758,9 +751,7 @@ func TestContractUpdate(t *testing.T) { ID: 95, Script: duplicateScript, Manifest: manifest.Manifest{ - ABI: manifest.ABI{ - Hash: hash.Hash160(duplicateScript), - }, + ABI: manifest.ABI{}, }, })) v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) @@ -792,7 +783,6 @@ func TestContractUpdate(t *testing.T) { Script: newScript, Manifest: cs.Manifest, } - expected.Manifest.ABI.Hash = hash.Hash160(newScript) _ = expected.ScriptHash() require.Equal(t, expected, actual) @@ -809,27 +799,10 @@ func TestContractUpdate(t *testing.T) { require.Error(t, contractUpdate(ic)) }) - t.Run("update manifest, bad contract hash", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) - manifest := &manifest.Manifest{ - ABI: manifest.ABI{ - Hash: util.Uint160{4, 5, 6}, - }, - } - manifestBytes, err := json.Marshal(manifest) - require.NoError(t, err) - putArgsOnStack(stackitem.Null{}, manifestBytes) - - require.Error(t, contractUpdate(ic)) - }) - t.Run("update manifest, positive", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) manifest := &manifest.Manifest{ - ABI: manifest.ABI{ - Hash: cs.ScriptHash(), - }, + ABI: manifest.ABI{}, } manifestBytes, err := json.Marshal(manifest) require.NoError(t, err) @@ -861,9 +834,7 @@ func TestContractUpdate(t *testing.T) { v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) newScript := []byte{12, 13, 14} newManifest := manifest.Manifest{ - ABI: manifest.ABI{ - Hash: hash.Hash160(newScript), - }, + ABI: manifest.ABI{}, } newManifestBytes, err := json.Marshal(newManifest) require.NoError(t, err) @@ -880,7 +851,6 @@ func TestContractUpdate(t *testing.T) { Script: newScript, Manifest: newManifest, } - expected.Manifest.ABI.Hash = hash.Hash160(newScript) _ = expected.ScriptHash() require.Equal(t, expected, actual) @@ -926,7 +896,6 @@ func TestContractCreateDeploy(t *testing.T) { Script: append(cs.Script, byte(opcode.RET)), Manifest: cs.Manifest, } - newCs.Manifest.ABI.Hash = hash.Hash160(newCs.Script) putArgs(newCs) require.NoError(t, contractUpdate(ic)) require.NoError(t, v.Run()) @@ -992,7 +961,7 @@ func TestMethodCallback(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(currCs)) ic.Functions = append(ic.Functions, systemInterops) - rawHash := cs.Manifest.ABI.Hash.BytesBE() + rawHash := hash.Hash160(cs.Script).BytesBE() t.Run("Invalid", func(t *testing.T) { runInvalid := func(args ...interface{}) func(t *testing.T) { diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index 9d9a310f7..0ece9ebed 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -50,7 +50,7 @@ func getOracleContractState(h util.Uint160) *state.Contract { emit.Syscall(w.BinWriter, interopnames.SystemStoragePut) emit.Opcodes(w.BinWriter, opcode.RET) - m := manifest.NewManifest(h, "TestOracle") + m := manifest.NewManifest("TestOracle") m.ABI.Methods = []manifest.Method{ { Name: "requestURL", diff --git a/pkg/core/state/contract_test.go b/pkg/core/state/contract_test.go index 90da814ea..92dfcb0f6 100644 --- a/pkg/core/state/contract_test.go +++ b/pkg/core/state/contract_test.go @@ -14,7 +14,7 @@ func TestEncodeDecodeContractState(t *testing.T) { script := []byte("testscript") h := hash.Hash160(script) - m := manifest.NewManifest(h, "Test") + m := manifest.NewManifest("Test") m.ABI.Methods = []manifest.Method{{ Name: "main", Parameters: []manifest.Parameter{ diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 3fc1723b0..1d414f6b9 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -19,7 +19,6 @@ import ( "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/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/request" @@ -327,13 +326,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ } return c.GetContractStateByHash(hash) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"name":"Test","abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { panic(err) } - m := manifest.NewManifest(hash.Hash160(script), "Test") + m := manifest.NewManifest("Test") cs := &state.Contract{ ID: 0, Script: script, @@ -348,13 +347,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetContractStateByAddressOrName("NWiu5oejTu925aeL9Hc1LX8SvaJhE23h15") }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"name":"Test","abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { panic(err) } - m := manifest.NewManifest(hash.Hash160(script), "Test") + m := manifest.NewManifest("Test") cs := &state.Contract{ ID: 0, Script: script, @@ -369,13 +368,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { return c.GetContractStateByID(0) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"name":"Test","abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"name":"Test","abi":{"methods":[],"events":[]},"groups":[],"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { panic(err) } - m := manifest.NewManifest(hash.Hash160(script), "Test") + m := manifest.NewManifest("Test") cs := &state.Contract{ ID: 0, Script: script, diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 8b813bd2e..78de6bf24 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -32,9 +32,8 @@ const ( // ABI represents a contract application binary interface. type ABI struct { - Hash util.Uint160 `json:"hash"` - Methods []Method `json:"methods"` - Events []Event `json:"events"` + Methods []Method `json:"methods"` + Events []Event `json:"events"` } // Manifest represens contract metadata. @@ -57,11 +56,10 @@ type Manifest struct { } // NewManifest returns new manifest with necessary fields initialized. -func NewManifest(h util.Uint160, name string) *Manifest { +func NewManifest(name string) *Manifest { m := &Manifest{ Name: name, ABI: ABI{ - Hash: h, Methods: []Method{}, Events: []Event{}, }, @@ -74,8 +72,8 @@ func NewManifest(h util.Uint160, name string) *Manifest { } // DefaultManifest returns default contract manifest. -func DefaultManifest(h util.Uint160, name string) *Manifest { - m := NewManifest(h, name) +func DefaultManifest(name string) *Manifest { + m := NewManifest(name) m.Permissions = []Permission{*NewPermission(PermissionWildcard)} return m } @@ -101,26 +99,22 @@ func (a *ABI) GetEvent(name string) *Event { } // CanCall returns true is current contract is allowed to call -// method of another contract. -func (m *Manifest) CanCall(toCall *Manifest, method string) bool { +// method of another contract with specified hash. +func (m *Manifest) CanCall(hash util.Uint160, toCall *Manifest, method string) bool { // this if is not present in the original code but should probably be here if toCall.SafeMethods.Contains(method) { return true } for i := range m.Permissions { - if m.Permissions[i].IsAllowed(toCall, method) { + if m.Permissions[i].IsAllowed(hash, toCall, method) { return true } } return false } -// IsValid checks whether the given hash is the one specified in manifest and -// verifies it against all the keys in manifest groups. +// IsValid checks whether the hash given is correct wrt manifest's groups. func (m *Manifest) IsValid(hash util.Uint160) bool { - if m.ABI.Hash != hash { - return false - } for _, g := range m.Groups { if !g.IsValid(hash) { return false diff --git a/pkg/smartcontract/manifest/manifest_test.go b/pkg/smartcontract/manifest/manifest_test.go index 2c310dc26..9fd1ca2e4 100644 --- a/pkg/smartcontract/manifest/manifest_test.go +++ b/pkg/smartcontract/manifest/manifest_test.go @@ -13,40 +13,40 @@ import ( // https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs#L10 func TestManifest_MarshalJSON(t *testing.T) { t.Run("default", func(t *testing.T) { - s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` + s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` m := testUnmarshalMarshalManifest(t, s) - require.Equal(t, DefaultManifest(util.Uint160{}, "Test"), m) + require.Equal(t, DefaultManifest("Test"), m) }) t.Run("permissions", func(t *testing.T) { - s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safemethods":[],"extra":null}` + s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safemethods":[],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("safe methods", func(t *testing.T) { - s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":["balanceOf"],"extra":null}` + s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":["balanceOf"],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("trust", func(t *testing.T) { - s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safemethods":[],"extra":null}` + s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safemethods":[],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("groups", func(t *testing.T) { - s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"supportedstandards":[],"name":"Test","abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` + s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("extra", func(t *testing.T) { - s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":{"key":"value"}}` + s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":{"key":"value"}}` testUnmarshalMarshalManifest(t, s) }) } func testUnmarshalMarshalManifest(t *testing.T, s string) *Manifest { js := []byte(s) - c := NewManifest(util.Uint160{}, "Test") + c := NewManifest("Test") require.NoError(t, json.Unmarshal(js, c)) data, err := json.Marshal(c) @@ -58,43 +58,43 @@ func testUnmarshalMarshalManifest(t *testing.T, s string) *Manifest { func TestManifest_CanCall(t *testing.T) { t.Run("safe methods", func(t *testing.T) { - man1 := NewManifest(util.Uint160{}, "Test1") - man2 := DefaultManifest(util.Uint160{}, "Test2") - require.False(t, man1.CanCall(man2, "method1")) + man1 := NewManifest("Test1") + man2 := DefaultManifest("Test2") + require.False(t, man1.CanCall(util.Uint160{}, man2, "method1")) man2.SafeMethods.Add("method1") - require.True(t, man1.CanCall(man2, "method1")) + require.True(t, man1.CanCall(util.Uint160{}, man2, "method1")) }) t.Run("wildcard permission", func(t *testing.T) { - man1 := DefaultManifest(util.Uint160{}, "Test1") - man2 := DefaultManifest(util.Uint160{}, "Test2") - require.True(t, man1.CanCall(man2, "method1")) + man1 := DefaultManifest("Test1") + man2 := DefaultManifest("Test2") + require.True(t, man1.CanCall(util.Uint160{}, man2, "method1")) }) } func TestPermission_IsAllowed(t *testing.T) { - manifest := DefaultManifest(util.Uint160{}, "Test") + manifest := DefaultManifest("Test") t.Run("wildcard", func(t *testing.T) { perm := NewPermission(PermissionWildcard) - require.True(t, perm.IsAllowed(manifest, "AAA")) + require.True(t, perm.IsAllowed(util.Uint160{}, manifest, "AAA")) }) t.Run("hash", func(t *testing.T) { perm := NewPermission(PermissionHash, util.Uint160{}) - require.True(t, perm.IsAllowed(manifest, "AAA")) + require.True(t, perm.IsAllowed(util.Uint160{}, manifest, "AAA")) t.Run("restrict methods", func(t *testing.T) { perm.Methods.Restrict() - require.False(t, perm.IsAllowed(manifest, "AAA")) + require.False(t, perm.IsAllowed(util.Uint160{}, manifest, "AAA")) perm.Methods.Add("AAA") - require.True(t, perm.IsAllowed(manifest, "AAA")) + require.True(t, perm.IsAllowed(util.Uint160{}, manifest, "AAA")) }) }) t.Run("invalid hash", func(t *testing.T) { perm := NewPermission(PermissionHash, util.Uint160{1}) - require.False(t, perm.IsAllowed(manifest, "AAA")) + require.False(t, perm.IsAllowed(util.Uint160{}, manifest, "AAA")) }) priv, err := keys.NewPrivateKey() @@ -103,29 +103,25 @@ func TestPermission_IsAllowed(t *testing.T) { t.Run("group", func(t *testing.T) { perm := NewPermission(PermissionGroup, priv.PublicKey()) - require.True(t, perm.IsAllowed(manifest, "AAA")) + require.True(t, perm.IsAllowed(util.Uint160{}, manifest, "AAA")) }) t.Run("invalid group", func(t *testing.T) { priv2, err := keys.NewPrivateKey() require.NoError(t, err) perm := NewPermission(PermissionGroup, priv2.PublicKey()) - require.False(t, perm.IsAllowed(manifest, "AAA")) + require.False(t, perm.IsAllowed(util.Uint160{}, manifest, "AAA")) }) } func TestIsValid(t *testing.T) { contractHash := util.Uint160{1, 2, 3} - m := NewManifest(contractHash, "Test") + m := NewManifest("Test") t.Run("valid, no groups", func(t *testing.T) { require.True(t, m.IsValid(contractHash)) }) - t.Run("invalid, no groups", func(t *testing.T) { - require.False(t, m.IsValid(util.Uint160{9, 8, 7})) - }) - t.Run("with groups", func(t *testing.T) { m.Groups = make([]Group, 3) pks := make([]*keys.PrivateKey, 3) diff --git a/pkg/smartcontract/manifest/permission.go b/pkg/smartcontract/manifest/permission.go index 064d24d99..00e433dfe 100644 --- a/pkg/smartcontract/manifest/permission.go +++ b/pkg/smartcontract/manifest/permission.go @@ -84,12 +84,12 @@ func (d *PermissionDesc) Group() *keys.PublicKey { } // IsAllowed checks if method is allowed to be executed. -func (p *Permission) IsAllowed(m *Manifest, method string) bool { +func (p *Permission) IsAllowed(hash util.Uint160, m *Manifest, method string) bool { switch p.Contract.Type { case PermissionWildcard: return true case PermissionHash: - if !p.Contract.Hash().Equals(m.ABI.Hash) { + if !p.Contract.Hash().Equals(hash) { return false } case PermissionGroup: diff --git a/pkg/smartcontract/manifest/standard/comply_test.go b/pkg/smartcontract/manifest/standard/comply_test.go index 1fd5b8caf..34e0b52fd 100644 --- a/pkg/smartcontract/manifest/standard/comply_test.go +++ b/pkg/smartcontract/manifest/standard/comply_test.go @@ -6,7 +6,6 @@ import ( "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/stretchr/testify/require" ) @@ -106,7 +105,7 @@ func TestComplyValid(t *testing.T) { } func TestCheck(t *testing.T) { - m := manifest.NewManifest(util.Uint160{}, "Test") + m := manifest.NewManifest("Test") require.Error(t, Check(m, manifest.NEP17StandardName)) require.NoError(t, Check(nep17, manifest.NEP17StandardName)) From c5e39dfabf4cf03b008a31cd6423ac88310ecfca Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 18 Nov 2020 12:57:44 +0300 Subject: [PATCH 03/11] nef: forbid NEFs with zero-length scripts --- pkg/smartcontract/nef/nef.go | 4 ++++ pkg/smartcontract/nef/nef_test.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/pkg/smartcontract/nef/nef.go b/pkg/smartcontract/nef/nef.go index c4958e500..effb07ab6 100644 --- a/pkg/smartcontract/nef/nef.go +++ b/pkg/smartcontract/nef/nef.go @@ -203,6 +203,10 @@ func (n *File) DecodeBinary(r *io.BinReader) { return } n.Script = r.ReadVarBytes(MaxScriptLength) + if len(n.Script) == 0 { + r.Err = errors.New("empty script") + return + } if !hash.Hash160(n.Script).Equals(n.Header.ScriptHash) { r.Err = errors.New("script hashes mismatch") return diff --git a/pkg/smartcontract/nef/nef_test.go b/pkg/smartcontract/nef/nef_test.go index 01ecc2b00..229d786a2 100644 --- a/pkg/smartcontract/nef/nef_test.go +++ b/pkg/smartcontract/nef/nef_test.go @@ -37,6 +37,13 @@ func TestEncodeDecodeBinary(t *testing.T) { checkDecodeError(t, expected) }) + t.Run("zero-length script", func(t *testing.T) { + expected.Script = make([]byte, 0) + expected.Header.ScriptHash = hash.Hash160(expected.Script) + expected.Checksum = expected.Header.CalculateChecksum() + checkDecodeError(t, expected) + }) + t.Run("invalid script length", func(t *testing.T) { newScript := make([]byte, MaxScriptLength+1) expected.Script = newScript From 1cf1fe5d74ef96eec3c440c452d104d04fc9d4ca Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 18 Nov 2020 23:10:48 +0300 Subject: [PATCH 04/11] *: introduce stable contract hashes Follow neo-project/neo#2044. --- cli/contract_test.go | 10 +- cli/smartcontract/smart_contract.go | 13 +- cli/wallet/wallet.go | 2 +- internal/testchain/transaction.go | 37 ++++-- pkg/compiler/interop_test.go | 1 + pkg/core/blockchain_test.go | 12 +- pkg/core/dao/cacheddao.go | 2 +- pkg/core/dao/cacheddao_test.go | 1 + pkg/core/dao/dao.go | 23 ++-- pkg/core/dao/dao_test.go | 17 +-- pkg/core/helper_test.go | 25 ++-- pkg/core/interop/callback/method.go | 2 +- pkg/core/interop/contract/call.go | 7 +- pkg/core/interop_neo.go | 143 +++++++++++----------- pkg/core/interop_neo_test.go | 2 + pkg/core/interop_system.go | 3 + pkg/core/interop_system_test.go | 158 +++++++++++++------------ pkg/core/native/interop.go | 1 + pkg/core/native_contract_test.go | 4 +- pkg/core/native_neo_test.go | 2 +- pkg/core/native_oracle_test.go | 17 +-- pkg/core/state/contract.go | 72 ++++------- pkg/core/state/contract_test.go | 26 ++-- pkg/rpc/client/client.go | 4 +- pkg/rpc/client/nep17.go | 6 +- pkg/rpc/client/rpc.go | 4 +- pkg/rpc/client/rpc_test.go | 7 +- pkg/rpc/server/server_test.go | 10 +- pkg/rpc/server/testdata/testblocks.acc | Bin 7476 -> 7530 bytes pkg/wallet/wallet.go | 4 +- pkg/wallet/wallet_test.go | 1 + scripts/gendump/main.go | 8 +- 32 files changed, 320 insertions(+), 304 deletions(-) diff --git a/cli/contract_test.go b/cli/contract_test.go index 7781f603a..3289e907e 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -10,9 +10,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/config" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/stretchr/testify/require" @@ -58,7 +56,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { res := new(result.Invoke) require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) - require.Equal(t, vm.HaltState.String(), res.State) + require.Equal(t, vm.HaltState.String(), res.State, res.FaultException) require.Len(t, res.Stack, 1) require.Equal(t, []byte("on create|sub create"), res.Stack[0].Value()) @@ -77,8 +75,6 @@ func TestComlileAndInvokeFunction(t *testing.T) { rawNef, err := ioutil.ReadFile(nefName) require.NoError(t, err) - realNef, err := nef.FileFromBytes(rawNef) - require.NoError(t, err) rawManifest, err := ioutil.ReadFile(manifestName) require.NoError(t, err) @@ -87,7 +83,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorAddr, h.StringLE(), "update", - "bytes:"+hex.EncodeToString(realNef.Script), + "bytes:"+hex.EncodeToString(rawNef), "bytes:"+hex.EncodeToString(rawManifest), ) e.checkTxPersisted(t, "Sent invocation transaction ") @@ -95,7 +91,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "testinvokefunction", "--rpc-endpoint", "http://"+e.RPC.Addr, - hash.Hash160(realNef.Script).StringLE(), "getValue") + h.StringLE(), "getValue") res := new(result.Invoke) require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 0e35439cb..e8555d0b0 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/pkg/compiler" + "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/rpc/request" @@ -734,13 +735,18 @@ func contractDeploy(ctx *cli.Context) error { if err != nil { return err } + sender, err := address.StringToUint160(acc.Address) + if err != nil { + return cli.NewExitError(err, 1) + } f, err := ioutil.ReadFile(in) if err != nil { return cli.NewExitError(err, 1) } + // Check the file. nefFile, err := nef.FileFromBytes(f) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to restore .nef file: %w", err), 1) + return cli.NewExitError(fmt.Errorf("failed to read .nef file: %w", err), 1) } manifestBytes, err := ioutil.ReadFile(manifestFile) @@ -761,7 +767,7 @@ func contractDeploy(ctx *cli.Context) error { return err } - txScript, err := request.CreateDeploymentScript(nefFile.Script, m) + txScript, err := request.CreateDeploymentScript(f, m) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", err), 1) } @@ -775,7 +781,8 @@ func contractDeploy(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1) } - fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", nefFile.Header.ScriptHash.StringLE()) + hash := state.CreateContractHash(sender, nefFile.Script) + fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", hash.StringLE()) fmt.Fprintln(ctx.App.Writer, txHash.StringLE()) return nil } diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index a494a1165..6ab5eaa38 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -437,7 +437,7 @@ func importDeployed(ctx *cli.Context) error { if md == nil { return cli.NewExitError("contract has no `verify` method", 1) } - acc.Address = address.Uint160ToString(cs.ScriptHash()) + acc.Address = address.Uint160ToString(cs.Hash) acc.Contract.Script = cs.Script acc.Contract.Parameters = acc.Contract.Parameters[:0] for _, p := range md.Parameters { diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index d2a37a134..02bf32791 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -5,13 +5,16 @@ import ( gio "io" "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "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" @@ -46,28 +49,46 @@ func NewTransferFromOwner(bc blockchainer.Blockchainer, contractHash, to util.Ui } // NewDeployTx returns new deployment tx for contract with name with Go code read from r. -func NewDeployTx(name string, r gio.Reader) (*transaction.Transaction, []byte, error) { +func NewDeployTx(name string, sender util.Uint160, r gio.Reader) (*transaction.Transaction, util.Uint160, error) { + // nef.NewFile() cares about version a lot. + config.Version = "0.90.0-test" + avm, di, err := compiler.CompileWithDebugInfo(name, r) if err != nil { - return nil, nil, err + return nil, util.Uint160{}, err + } + + ne, err := nef.NewFile(avm) + if err != nil { + return nil, util.Uint160{}, err + } + neb, err := ne.Bytes() + if err != nil { + return nil, util.Uint160{}, err } - w := io.NewBufBinWriter() m, err := di.ConvertToManifest(name, nil) if err != nil { - return nil, nil, err + return nil, util.Uint160{}, err } bs, err := json.Marshal(m) if err != nil { - return nil, nil, err + return nil, util.Uint160{}, err } + + w := io.NewBufBinWriter() emit.Bytes(w.BinWriter, bs) - emit.Bytes(w.BinWriter, avm) + emit.Bytes(w.BinWriter, neb) emit.Syscall(w.BinWriter, interopnames.SystemContractCreate) if w.Err != nil { - return nil, nil, err + return nil, util.Uint160{}, w.Err } - return transaction.New(Network(), w.Bytes(), 100*native.GASFactor), avm, nil + txScript := w.Bytes() + tx := transaction.New(Network(), txScript, 100*native.GASFactor) + tx.Signers = []transaction.Signer{{Account: sender}} + h := state.CreateContractHash(tx.Sender(), avm) + + return tx, h, nil } // SignTx signs provided transactions with validator keys. diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 7150bfb01..a87bcbfbe 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -107,6 +107,7 @@ func TestAppCall(t *testing.T) { ih := hash.Hash160(inner) ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), nil, nil, nil, zaptest.NewLogger(t)) require.NoError(t, ic.DAO.PutContractState(&state.Contract{ + Hash: ih, Script: inner, Manifest: *m, })) diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 6e27cc3b2..b243f9385 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -940,7 +940,7 @@ func TestVerifyHashAgainstScript(t *testing.T) { gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO) t.Run("Contract", func(t *testing.T) { t.Run("Missing", func(t *testing.T) { - newH := cs.ScriptHash() + newH := cs.Hash newH[0] = ^newH[0] w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} _, err := bc.verifyHashAgainstScript(newH, w, ic, gas) @@ -948,17 +948,17 @@ func TestVerifyHashAgainstScript(t *testing.T) { }) t.Run("Invalid", func(t *testing.T) { w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - _, err := bc.verifyHashAgainstScript(csInvalid.ScriptHash(), w, ic, gas) + _, err := bc.verifyHashAgainstScript(csInvalid.Hash, w, ic, gas) require.True(t, errors.Is(err, ErrInvalidVerificationContract)) }) t.Run("ValidSignature", func(t *testing.T) { w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - _, err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas) + _, err := bc.verifyHashAgainstScript(cs.Hash, w, ic, gas) require.NoError(t, err) }) t.Run("InvalidSignature", func(t *testing.T) { w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH3)}} - _, err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas) + _, err := bc.verifyHashAgainstScript(cs.Hash, w, ic, gas) require.True(t, errors.Is(err, ErrVerificationFailed)) }) }) @@ -1071,7 +1071,7 @@ func TestIsTxStillRelevant(t *testing.T) { currentHeight := blockchain.GetHeight() return currentHeight < %d }`, bc.BlockHeight()+2) // deploy + next block - txDeploy, avm, err := testchain.NewDeployTx("TestVerify", strings.NewReader(src)) + txDeploy, h, err := testchain.NewDeployTx("TestVerify", neoOwner, strings.NewReader(src)) require.NoError(t, err) txDeploy.ValidUntilBlock = bc.BlockHeight() + 1 addSigners(txDeploy) @@ -1080,7 +1080,7 @@ func TestIsTxStillRelevant(t *testing.T) { tx := newTx(t) tx.Signers = append(tx.Signers, transaction.Signer{ - Account: hash.Hash160(avm), + Account: h, Scopes: transaction.None, }) tx.NetworkFee += 1_000_000 diff --git a/pkg/core/dao/cacheddao.go b/pkg/core/dao/cacheddao.go index a11727e88..037dc64a5 100644 --- a/pkg/core/dao/cacheddao.go +++ b/pkg/core/dao/cacheddao.go @@ -42,7 +42,7 @@ func (cd *Cached) GetContractState(hash util.Uint160) (*state.Contract, error) { // PutContractState puts given contract state into the given store. func (cd *Cached) PutContractState(cs *state.Contract) error { - cd.contracts[cs.ScriptHash()] = cs + cd.contracts[cs.Hash] = cs return cd.DAO.PutContractState(cs) } diff --git a/pkg/core/dao/cacheddao_test.go b/pkg/core/dao/cacheddao_test.go index d54f2ea89..f38987e85 100644 --- a/pkg/core/dao/cacheddao_test.go +++ b/pkg/core/dao/cacheddao_test.go @@ -27,6 +27,7 @@ func TestCachedDaoContracts(t *testing.T) { cs := &state.Contract{ ID: 123, + Hash: sh, Script: script, Manifest: *m, } diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 48ae63b95..b1fb35a7d 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -136,20 +136,23 @@ func (dao *Simple) GetContractState(hash util.Uint160) (*state.Contract, error) if err != nil { return nil, err } - if contract.ScriptHash() != hash { - return nil, fmt.Errorf("found script hash is not equal to expected") - } return contract, nil } // PutContractState puts given contract state into the given store. func (dao *Simple) PutContractState(cs *state.Contract) error { - key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().BytesBE()) + key := storage.AppendPrefix(storage.STContract, cs.Hash.BytesBE()) if err := dao.Put(cs, key); err != nil { return err } - return dao.putContractScriptHash(cs) + if cs.UpdateCounter != 0 { // Update. + return nil + } + key = key[:5] + key[0] = byte(storage.STContractID) + binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID)) + return dao.Store.Put(key, cs.Hash.BytesBE()) } // DeleteContractState deletes given contract state in the given store. @@ -173,16 +176,6 @@ func (dao *Simple) GetAndUpdateNextContractID() (int32, error) { return id, dao.Store.Put(key, data) } -// putContractScriptHash puts given contract script hash into the given store. -// It's a private method because it should be used after PutContractState to keep -// ID-Hash pair always up-to-date. -func (dao *Simple) putContractScriptHash(cs *state.Contract) error { - key := make([]byte, 5) - key[0] = byte(storage.STContractID) - binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID)) - return dao.Store.Put(key, cs.ScriptHash().BytesBE()) -} - // GetContractScriptHash returns script hash of the contract with the specified ID. // Contract with the script hash may be destroyed. func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) { diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index 9a583084e..a1a022eea 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -10,6 +10,7 @@ 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/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -44,24 +45,26 @@ func (t *TestSerializable) DecodeBinary(reader *io.BinReader) { func TestPutAndGetContractState(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - contractState := &state.Contract{Script: []byte{}} - hash := contractState.ScriptHash() + script := []byte{} + h := hash.Hash160(script) + contractState := &state.Contract{Hash: h, Script: script} err := dao.PutContractState(contractState) require.NoError(t, err) - gotContractState, err := dao.GetContractState(hash) + gotContractState, err := dao.GetContractState(contractState.Hash) require.NoError(t, err) require.Equal(t, contractState, gotContractState) } func TestDeleteContractState(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - contractState := &state.Contract{Script: []byte{}} - hash := contractState.ScriptHash() + script := []byte{} + h := hash.Hash160(script) + contractState := &state.Contract{Hash: h, Script: script} err := dao.PutContractState(contractState) require.NoError(t, err) - err = dao.DeleteContractState(hash) + err = dao.DeleteContractState(h) require.NoError(t, err) - gotContractState, err := dao.GetContractState(hash) + gotContractState, err := dao.GetContractState(h) require.Error(t, err) require.Nil(t, gotContractState) } diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 796705a07..91dfcbeca 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -272,10 +272,9 @@ func initBasicChain(t *testing.T, bc *Blockchain) { require.NoError(t, err) // Push some contract into the chain. - txDeploy, avm := newDeployTx(t, prefix+"test_contract.go", "Rubl") + txDeploy, cHash := newDeployTx(t, priv0ScriptHash, prefix+"test_contract.go", "Rubl") txDeploy.Nonce = getNextNonce() txDeploy.ValidUntilBlock = validUntilBlock - txDeploy.Signers = []transaction.Signer{{Account: priv0ScriptHash}} require.NoError(t, addNetworkFee(bc, txDeploy, acc0)) require.NoError(t, acc0.SignTx(txDeploy)) b = bc.newBlock(txDeploy) @@ -285,7 +284,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) { // Now invoke this contract. script := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "putValue", "testkey", "testvalue") + emit.AppCallWithOperationAndArgs(script.BinWriter, cHash, "putValue", "testkey", "testvalue") txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor) txInv.Nonce = getNextNonce() @@ -314,16 +313,15 @@ func initBasicChain(t *testing.T, bc *Blockchain) { b = bc.newBlock(txNeo0to1) require.NoError(t, bc.AddBlock(b)) - sh := hash.Hash160(avm) w := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(w.BinWriter, sh, "init") + emit.AppCallWithOperationAndArgs(w.BinWriter, cHash, "init") initTx := transaction.New(testchain.Network(), w.Bytes(), 1*native.GASFactor) initTx.Nonce = getNextNonce() initTx.ValidUntilBlock = validUntilBlock initTx.Signers = []transaction.Signer{{Account: priv0ScriptHash}} require.NoError(t, addNetworkFee(bc, initTx, acc0)) require.NoError(t, acc0.SignTx(initTx)) - transferTx := newNEP17Transfer(sh, sh, priv0.GetScriptHash(), 1000) + transferTx := newNEP17Transfer(cHash, cHash, priv0.GetScriptHash(), 1000) transferTx.Nonce = getNextNonce() transferTx.ValidUntilBlock = validUntilBlock transferTx.Signers = []transaction.Signer{ @@ -341,7 +339,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) { require.NoError(t, bc.AddBlock(b)) t.Logf("recieveRublesTx: %v", transferTx.Hash().StringLE()) - transferTx = newNEP17Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123) + transferTx = newNEP17Transfer(cHash, priv0.GetScriptHash(), priv1.GetScriptHash(), 123) transferTx.Nonce = getNextNonce() transferTx.ValidUntilBlock = validUntilBlock transferTx.Signers = []transaction.Signer{ @@ -360,10 +358,9 @@ func initBasicChain(t *testing.T, bc *Blockchain) { t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE()) // Push verification contract into the chain. - txDeploy2, _ := newDeployTx(t, prefix+"verification_contract.go", "Verify") + txDeploy2, _ := newDeployTx(t, priv0ScriptHash, prefix+"verification_contract.go", "Verify") txDeploy2.Nonce = getNextNonce() txDeploy2.ValidUntilBlock = validUntilBlock - txDeploy2.Signers = []transaction.Signer{{Account: priv0ScriptHash}} require.NoError(t, addNetworkFee(bc, txDeploy2, acc0)) require.NoError(t, acc0.SignTx(txDeploy2)) b = bc.newBlock(txDeploy2) @@ -379,15 +376,13 @@ func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs .. return transaction.New(testchain.Network(), script, 10000000) } -func newDeployTx(t *testing.T, name, ctrName string) (*transaction.Transaction, []byte) { +func newDeployTx(t *testing.T, sender util.Uint160, name, ctrName string) (*transaction.Transaction, util.Uint160) { c, err := ioutil.ReadFile(name) require.NoError(t, err) - tx, avm, err := testchain.NewDeployTx(ctrName, bytes.NewReader(c)) + tx, h, err := testchain.NewDeployTx(ctrName, sender, bytes.NewReader(c)) require.NoError(t, err) - t.Logf("contractHash (%s): %s", name, hash.Hash160(avm).StringLE()) - t.Logf("contractScript: %x", avm) - - return tx, avm + t.Logf("contractHash (%s): %s", name, h.StringLE()) + return tx, h } func addSigners(txs ...*transaction.Transaction) { diff --git a/pkg/core/interop/callback/method.go b/pkg/core/interop/callback/method.go index 78947c3a1..ada8e2d41 100644 --- a/pkg/core/interop/callback/method.go +++ b/pkg/core/interop/callback/method.go @@ -29,7 +29,7 @@ func (s *MethodCallback) ArgCount() int { func (s *MethodCallback) LoadContext(v *vm.VM, args []stackitem.Item) { v.Estack().PushVal(args) v.Estack().PushVal(s.method.Name) - v.Estack().PushVal(s.contract.ScriptHash().BytesBE()) + v.Estack().PushVal(s.contract.Hash.BytesBE()) } // CreateFromMethod creates callback for a contract method. diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index b8f7a8bd0..08f064c37 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -67,12 +67,11 @@ func CallExInternal(ic *interop.Context, cs *state.Contract, return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters)) } - u := cs.ScriptHash() - ic.VM.Invocations[u]++ - ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f) + ic.VM.Invocations[cs.Hash]++ + ic.VM.LoadScriptWithHash(cs.Script, cs.Hash, ic.VM.Context().GetCallFlags()&f) var isNative bool for i := range ic.Natives { - if ic.Natives[i].Metadata().Hash.Equals(u) { + if ic.Natives[i].Metadata().Hash.Equals(cs.Hash) { isNative = true break } diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 446ebfbcc..47d643731 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -6,15 +6,16 @@ import ( "encoding/json" "errors" "fmt" + "math" "sort" "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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/nef" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) @@ -60,39 +61,59 @@ func storageFind(ic *interop.Context) error { return nil } -// createContractStateFromVM pops all contract state elements from the VM -// evaluation stack, does a lot of checks and returns Contract if it -// succeeds. -func createContractStateFromVM(ic *interop.Context) (*state.Contract, error) { - script := ic.VM.Estack().Pop().Bytes() - if len(script) > MaxContractScriptSize { - return nil, errors.New("the script is too big") +// getNefAndManifestFromVM pops NEF and manifest from the VM's evaluation stack, +// does a lot of checks and returns deserialized structures if succeeds. +func getNefAndManifestFromVM(v *vm.VM) (*nef.File, *manifest.Manifest, error) { + // Always pop both elements. + nefBytes := v.Estack().Pop().BytesOrNil() + manifestBytes := v.Estack().Pop().BytesOrNil() + + if err := checkNonEmpty(nefBytes, math.MaxInt32); err != nil { // Upper limits are checked during NEF deserialization. + return nil, nil, fmt.Errorf("invalid NEF file: %w", err) } - manifestBytes := ic.VM.Estack().Pop().Bytes() - if len(manifestBytes) > manifest.MaxManifestSize { - return nil, errors.New("manifest is too big") + if err := checkNonEmpty(manifestBytes, manifest.MaxManifestSize); err != nil { + return nil, nil, fmt.Errorf("invalid manifest: %w", err) } - if !ic.VM.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) { - return nil, errGasLimitExceeded + + if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) { + return nil, nil, errGasLimitExceeded } - var m manifest.Manifest - err := json.Unmarshal(manifestBytes, &m) - if err != nil { - return nil, fmt.Errorf("unable to retrieve manifest from stack: %w", err) + var resManifest *manifest.Manifest + var resNef *nef.File + if nefBytes != nil { + nf, err := nef.FileFromBytes(nefBytes) + if err != nil { + return nil, nil, fmt.Errorf("invalid NEF file: %w", err) + } + resNef = &nf } - return &state.Contract{ - Script: script, - Manifest: m, - }, nil + if manifestBytes != nil { + resManifest = new(manifest.Manifest) + err := json.Unmarshal(manifestBytes, resManifest) + if err != nil { + return nil, nil, fmt.Errorf("invalid manifest: %w", err) + } + } + return resNef, resManifest, nil } // contractCreate creates a contract. func contractCreate(ic *interop.Context) error { - newcontract, err := createContractStateFromVM(ic) + neff, manif, err := getNefAndManifestFromVM(ic.VM) if err != nil { return err } - contract, err := ic.DAO.GetContractState(newcontract.ScriptHash()) + if neff == nil { + return errors.New("no valid NEF provided") + } + if manif == nil { + return errors.New("no valid manifest provided") + } + if ic.Tx == nil { + return errors.New("no transaction provided") + } + h := state.CreateContractHash(ic.Tx.Sender(), neff.Script) + contract, err := ic.DAO.GetContractState(h) if contract != nil && err == nil { return errors.New("contract already exists") } @@ -100,10 +121,15 @@ func contractCreate(ic *interop.Context) error { if err != nil { return err } - newcontract.ID = id - if !newcontract.Manifest.IsValid(newcontract.ScriptHash()) { + if !manif.IsValid(h) { return errors.New("failed to check contract script hash against manifest") } + newcontract := &state.Contract{ + ID: id, + Hash: h, + Script: neff.Script, + Manifest: *manif, + } if err := ic.DAO.PutContractState(newcontract); err != nil { return err } @@ -129,64 +155,33 @@ func checkNonEmpty(b []byte, max int) error { // contractUpdate migrates a contract. This method assumes that Manifest and Script // of the contract can be updated independently. func contractUpdate(ic *interop.Context) error { + neff, manif, err := getNefAndManifestFromVM(ic.VM) + if err != nil { + return err + } + if neff == nil && manif == nil { + return errors.New("both NEF and manifest are nil") + } contract, _ := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) if contract == nil { return errors.New("contract doesn't exist") } - script := ic.VM.Estack().Pop().BytesOrNil() - manifestBytes := ic.VM.Estack().Pop().BytesOrNil() - if script == nil && manifestBytes == nil { - return errors.New("both script and manifest are nil") + // if NEF was provided, update the contract script + if neff != nil { + contract.Script = neff.Script } - if err := checkNonEmpty(script, MaxContractScriptSize); err != nil { - return fmt.Errorf("invalid script size: %w", err) - } - if err := checkNonEmpty(manifestBytes, manifest.MaxManifestSize); err != nil { - return fmt.Errorf("invalid manifest size: %w", err) - } - if !ic.VM.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) { - return errGasLimitExceeded - } - // if script was provided, update the old contract script and Manifest.ABI hash - if script != nil { - newHash := hash.Hash160(script) - if newHash.Equals(contract.ScriptHash()) { - return errors.New("the script is the same") - } else if _, err := ic.DAO.GetContractState(newHash); err == nil { - return errors.New("contract already exists") - } - oldHash := contract.ScriptHash() - // re-write existing contract variable, as we need it to be up-to-date during manifest update - contract = &state.Contract{ - ID: contract.ID, - Script: script, - Manifest: contract.Manifest, - } - if err := ic.DAO.PutContractState(contract); err != nil { - return fmt.Errorf("failed to update script: %w", err) - } - if err := ic.DAO.DeleteContractState(oldHash); err != nil { - return fmt.Errorf("failed to update script: %w", err) - } - } - // if manifest was provided, update the old contract manifest and check associated - // storage items if needed - if manifestBytes != nil { - var newManifest manifest.Manifest - err := json.Unmarshal(manifestBytes, &newManifest) - if err != nil { - return fmt.Errorf("unable to retrieve manifest from stack: %w", err) - } - // we don't have to perform `GetContractState` one more time as it's already up-to-date - contract.Manifest = newManifest - if !contract.Manifest.IsValid(contract.ScriptHash()) { + // if manifest was provided, update the contract manifest + if manif != nil { + contract.Manifest = *manif + if !contract.Manifest.IsValid(contract.Hash) { return errors.New("failed to check contract script hash against new manifest") } - if err := ic.DAO.PutContractState(contract); err != nil { - return fmt.Errorf("failed to update manifest: %w", err) - } } + contract.UpdateCounter++ + if err := ic.DAO.PutContractState(contract); err != nil { + return fmt.Errorf("failed to update contract: %w", err) + } return callDeploy(ic, contract, true) } diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index d89c9485e..1eb23d3c9 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -16,6 +16,7 @@ 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/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" @@ -296,6 +297,7 @@ func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.C m := manifest.NewManifest("Test") contractState := &state.Contract{ Script: script, + Hash: hash.Hash160(script), Manifest: *m, ID: 123, } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 31822df6a..da9a36e2f 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -105,6 +105,9 @@ func contractToStackItem(cs *state.Contract) (stackitem.Item, error) { return nil, err } return stackitem.NewArray([]stackitem.Item{ + stackitem.Make(cs.ID), + stackitem.Make(cs.UpdateCounter), + stackitem.NewByteArray(cs.Hash.BytesBE()), stackitem.NewByteArray(cs.Script), stackitem.NewByteArray(manifest), }), nil diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index dc33fb969..11e80b7e2 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/callback" @@ -21,6 +22,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -204,7 +206,7 @@ func TestContractIsStandard(t *testing.T) { require.NoError(t, err) pub := priv.PublicKey() - err = ic.DAO.PutContractState(&state.Contract{ID: 42, Script: pub.GetVerificationScript()}) + err = ic.DAO.PutContractState(&state.Contract{ID: 42, Hash: pub.GetScriptHash(), Script: pub.GetVerificationScript()}) require.NoError(t, err) v.Estack().PushVal(pub.GetScriptHash().BytesBE()) @@ -213,7 +215,7 @@ func TestContractIsStandard(t *testing.T) { }) t.Run("contract stored, false", func(t *testing.T) { script := []byte{byte(opcode.PUSHT)} - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ID: 24, Script: script})) + require.NoError(t, ic.DAO.PutContractState(&state.Contract{ID: 24, Hash: hash.Hash160(script), Script: script})) v.Estack().PushVal(crypto.Hash160(script).BytesBE()) require.NoError(t, contractIsStandard(ic)) @@ -319,7 +321,7 @@ func TestBlockchainGetContractState(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) t.Run("positive", func(t *testing.T) { - v.Estack().PushVal(cs.ScriptHash().BytesBE()) + v.Estack().PushVal(cs.Hash.BytesBE()) require.NoError(t, bcGetContract(ic)) actual := v.Estack().Pop().Item() @@ -502,6 +504,7 @@ func getTestContractState() (*state.Contract, *state.Contract) { } cs := &state.Contract{ Script: script, + Hash: h, Manifest: *m, ID: 42, } @@ -519,6 +522,7 @@ func getTestContractState() (*state.Contract, *state.Contract) { return cs, &state.Contract{ Script: currScript, + Hash: hash.Hash160(currScript), Manifest: *m, ID: 123, } @@ -654,13 +658,31 @@ func TestContractCreate(t *testing.T) { v.GasLimit = -1 defer bc.Close() + // nef.NewFile() cares about version a lot. + config.Version = "0.90.0-test" putArgsOnStack := func() { manifest, err := json.Marshal(cs.Manifest) require.NoError(t, err) + ne, err := nef.NewFile(cs.Script) + require.NoError(t, err) + neb, err := ne.Bytes() + require.NoError(t, err) v.Estack().PushVal(manifest) - v.Estack().PushVal(cs.Script) + v.Estack().PushVal(neb) } + t.Run("no tx", func(t *testing.T) { + putArgsOnStack() + + require.Error(t, contractCreate(ic)) + }) + + ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) + var sender = util.Uint160{1, 2, 3} + ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) + cs.ID = 0 + cs.Hash = state.CreateContractHash(sender, cs.Script) + t.Run("positive", func(t *testing.T) { putArgsOnStack() @@ -670,8 +692,6 @@ func TestContractCreate(t *testing.T) { }) t.Run("contract already exists", func(t *testing.T) { - cs.Script = cs.Script[:len(cs.Script)-1] - require.NoError(t, ic.DAO.PutContractState(cs)) putArgsOnStack() require.Error(t, contractCreate(ic)) @@ -685,9 +705,12 @@ func compareContractStates(t *testing.T, expected *state.Contract, actual stacki expectedManifest, err := json.Marshal(expected.Manifest) require.NoError(t, err) - require.Equal(t, 2, len(act)) - require.Equal(t, expected.Script, act[0].Value().([]byte)) - require.Equal(t, expectedManifest, act[1].Value().([]byte)) + require.Equal(t, 5, len(act)) + require.Equal(t, expected.ID, int32(act[0].Value().(*big.Int).Int64())) + require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64())) + require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte)) + require.Equal(t, expected.Script, act[3].Value().([]byte)) + require.Equal(t, expectedManifest, act[4].Value().([]byte)) } func TestContractUpdate(t *testing.T) { @@ -697,12 +720,19 @@ func TestContractUpdate(t *testing.T) { putArgsOnStack := func(script, manifest interface{}) { v.Estack().PushVal(manifest) + b, ok := script.([]byte) + if ok { + ne, err := nef.NewFile(b) + require.NoError(t, err) + script, err = ne.Bytes() + require.NoError(t, err) + } v.Estack().PushVal(script) } t.Run("no args", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) putArgsOnStack(stackitem.Null{}, stackitem.Null{}) require.Error(t, contractUpdate(ic)) }) @@ -710,19 +740,20 @@ func TestContractUpdate(t *testing.T) { t.Run("no contract", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) v.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{8, 9, 7}, smartcontract.All) + putArgsOnStack([]byte{1}, stackitem.Null{}) require.Error(t, contractUpdate(ic)) }) t.Run("too large script", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) putArgsOnStack(make([]byte, MaxContractScriptSize+1), stackitem.Null{}) require.Error(t, contractUpdate(ic)) }) t.Run("too large manifest", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) putArgsOnStack(stackitem.Null{}, make([]byte, manifest.MaxManifestSize+1)) require.Error(t, contractUpdate(ic)) }) @@ -730,70 +761,43 @@ func TestContractUpdate(t *testing.T) { t.Run("gas limit exceeded", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) v.GasLimit = 0 - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) putArgsOnStack([]byte{1}, []byte{2}) require.Error(t, contractUpdate(ic)) }) - t.Run("update script, the same script", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.GasLimit = -1 - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) - putArgsOnStack(cs.Script, stackitem.Null{}) - - require.Error(t, contractUpdate(ic)) - }) - - t.Run("update script, already exists", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - duplicateScript := []byte{byte(opcode.PUSHDATA4)} - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ - ID: 95, - Script: duplicateScript, - Manifest: manifest.Manifest{ - ABI: manifest.ABI{}, - }, - })) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) - putArgsOnStack(duplicateScript, stackitem.Null{}) - - require.Error(t, contractUpdate(ic)) - }) - + v.GasLimit = -1 t.Run("update script, positive", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) t.Run("empty manifest", func(t *testing.T) { - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) newScript := []byte{9, 8, 7, 6, 5} putArgsOnStack(newScript, []byte{}) require.Error(t, contractUpdate(ic)) }) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) newScript := []byte{9, 8, 7, 6, 5} putArgsOnStack(newScript, stackitem.Null{}) require.NoError(t, contractUpdate(ic)) - // updated contract should have new scripthash - actual, err := ic.DAO.GetContractState(hash.Hash160(newScript)) + // updated contract should have the same scripthash + actual, err := ic.DAO.GetContractState(cs.Hash) require.NoError(t, err) expected := &state.Contract{ - ID: cs.ID, - Script: newScript, - Manifest: cs.Manifest, + ID: cs.ID, + UpdateCounter: 1, + Hash: cs.Hash, + Script: newScript, + Manifest: cs.Manifest, } - _ = expected.ScriptHash() require.Equal(t, expected, actual) - - // old contract should be deleted - _, err = ic.DAO.GetContractState(cs.ScriptHash()) - require.Error(t, err) }) t.Run("update manifest, bad manifest", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) putArgsOnStack(stackitem.Null{}, []byte{1, 2, 3}) require.Error(t, contractUpdate(ic)) @@ -808,30 +812,31 @@ func TestContractUpdate(t *testing.T) { require.NoError(t, err) t.Run("empty script", func(t *testing.T) { - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) putArgsOnStack([]byte{}, manifestBytes) require.Error(t, contractUpdate(ic)) }) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) putArgsOnStack(stackitem.Null{}, manifestBytes) require.NoError(t, contractUpdate(ic)) - // updated contract should have new scripthash - actual, err := ic.DAO.GetContractState(cs.ScriptHash()) + // updated contract should have old scripthash + actual, err := ic.DAO.GetContractState(cs.Hash) require.NoError(t, err) expected := &state.Contract{ - ID: cs.ID, - Script: cs.Script, - Manifest: *manifest, + ID: cs.ID, + UpdateCounter: 2, + Hash: cs.Hash, + Script: cs.Script, + Manifest: *manifest, } - _ = expected.ScriptHash() require.Equal(t, expected, actual) }) t.Run("update both script and manifest", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) + v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) newScript := []byte{12, 13, 14} newManifest := manifest.Manifest{ ABI: manifest.ABI{}, @@ -844,19 +849,16 @@ func TestContractUpdate(t *testing.T) { require.NoError(t, contractUpdate(ic)) // updated contract should have new script and manifest - actual, err := ic.DAO.GetContractState(hash.Hash160(newScript)) + actual, err := ic.DAO.GetContractState(cs.Hash) require.NoError(t, err) expected := &state.Contract{ - ID: cs.ID, - Script: newScript, - Manifest: newManifest, + ID: cs.ID, + UpdateCounter: 3, + Hash: cs.Hash, + Script: newScript, + Manifest: newManifest, } - _ = expected.ScriptHash() require.Equal(t, expected, actual) - - // old contract should be deleted - _, err = ic.DAO.GetContractState(cs.ScriptHash()) - require.Error(t, err) }) } @@ -871,28 +873,37 @@ func TestContractCreateDeploy(t *testing.T) { rawManifest, err := json.Marshal(cs.Manifest) require.NoError(t, err) v.Estack().PushVal(rawManifest) - v.Estack().PushVal(cs.Script) + ne, err := nef.NewFile(cs.Script) + require.NoError(t, err) + b, err := ne.Bytes() + require.NoError(t, err) + v.Estack().PushVal(b) } cs, currCs := getTestContractState() + ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) + var sender = util.Uint160{1, 2, 3} + ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) v.LoadScriptWithFlags([]byte{byte(opcode.RET)}, smartcontract.All) putArgs(cs) require.NoError(t, contractCreate(ic)) require.NoError(t, ic.VM.Run()) - v.LoadScriptWithFlags(currCs.Script, smartcontract.All) + cs.Hash = state.CreateContractHash(sender, cs.Script) + v.LoadScriptWithHash(currCs.Script, cs.Hash, smartcontract.All) err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty, nil) require.NoError(t, err) require.NoError(t, v.Run()) require.Equal(t, "create", v.Estack().Pop().String()) - v.LoadScriptWithFlags(cs.Script, smartcontract.All) + v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All) md := cs.Manifest.ABI.GetMethod("justReturn") v.Jump(v.Context(), md.Offset) t.Run("Update", func(t *testing.T) { newCs := &state.Contract{ ID: cs.ID, + Hash: cs.Hash, Script: append(cs.Script, byte(opcode.RET)), Manifest: cs.Manifest, } @@ -900,7 +911,7 @@ func TestContractCreateDeploy(t *testing.T) { require.NoError(t, contractUpdate(ic)) require.NoError(t, v.Run()) - v.LoadScriptWithFlags(currCs.Script, smartcontract.All) + v.LoadScriptWithHash(currCs.Script, cs.Hash, smartcontract.All) err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty, nil) require.NoError(t, err) require.NoError(t, v.Run()) @@ -1219,6 +1230,7 @@ func TestRuntimeCheckWitness(t *testing.T) { contractScriptHash := hash.Hash160(contractScript) contractState := &state.Contract{ ID: 15, + Hash: contractScriptHash, Script: contractScript, Manifest: manifest.Manifest{ Groups: []manifest.Group{{PublicKey: pk.PublicKey()}}, diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index fef3ecbc4..fd02c6340 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -20,6 +20,7 @@ func Deploy(ic *interop.Context) error { cs := &state.Contract{ ID: md.ContractID, + Hash: md.Hash, Script: md.Script, Manifest: md.Manifest, } diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 221dd35a7..2f23752bb 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -149,6 +149,7 @@ func TestNativeContract_Invoke(t *testing.T) { err := chain.dao.PutContractState(&state.Contract{ Script: tn.meta.Script, + Hash: tn.meta.Hash, Manifest: tn.meta.Manifest, }) require.NoError(t, err) @@ -221,6 +222,7 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { chain.registerNative(tn) err := chain.dao.PutContractState(&state.Contract{ + Hash: tn.meta.Hash, Script: tn.meta.Script, Manifest: tn.meta.Manifest, }) @@ -230,7 +232,7 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { require.NoError(t, chain.dao.PutContractState(cs)) t.Run("non-native, no return", func(t *testing.T) { - res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.ScriptHash(), "justReturn", []interface{}{}) + res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{}) require.NoError(t, err) checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty }) diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 6dae37cd4..aecbf4bdd 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -313,7 +313,7 @@ func TestNEO_TransferOnPayment(t *testing.T) { require.NoError(t, bc.dao.PutContractState(cs)) const amount = 2 - tx := transferTokenFromMultisigAccount(t, bc, cs.ScriptHash(), bc.contracts.NEO.Hash, amount) + tx := transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount) aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) require.NoError(t, err) require.Equal(t, vm.HaltState, aer[0].VMState) diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index 0ece9ebed..7cea35cea 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -12,6 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/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/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -81,8 +82,10 @@ func getOracleContractState(h util.Uint160) *state.Contract { perm.Methods.Add("request") m.Permissions = append(m.Permissions, *perm) + script := w.Bytes() return &state.Contract{ - Script: w.Bytes(), + Script: script, + Hash: hash.Hash160(script), Manifest: *m, ID: 42, } @@ -111,7 +114,7 @@ func TestOracle_Request(t *testing.T) { gasForResponse := int64(2000_1234) var filter = "flt" userData := []byte("custom info") - txHash := putOracleRequest(t, cs.ScriptHash(), bc, "url", &filter, userData, gasForResponse) + txHash := putOracleRequest(t, cs.Hash, bc, "url", &filter, userData, gasForResponse) req, err := orc.GetRequestInternal(bc.dao, 1) require.NotNil(t, req) @@ -119,7 +122,7 @@ func TestOracle_Request(t *testing.T) { require.Equal(t, txHash, req.OriginalTxID) require.Equal(t, "url", req.URL) require.Equal(t, filter, *req.Filter) - require.Equal(t, cs.ScriptHash(), req.CallbackContract) + require.Equal(t, cs.Hash, req.CallbackContract) require.Equal(t, "handle", req.CallbackMethod) require.Equal(t, uint64(gasForResponse), req.GasForResponse) @@ -189,7 +192,7 @@ func TestOracle_Request(t *testing.T) { t.Run("ErrorOnFinish", func(t *testing.T) { const reqID = 2 - putOracleRequest(t, cs.ScriptHash(), bc, "url", nil, []byte{1, 2}, gasForResponse) + putOracleRequest(t, cs.Hash, bc, "url", nil, []byte{1, 2}, gasForResponse) _, err := orc.GetRequestInternal(bc.dao, reqID) // ensure ID is 2 require.NoError(t, err) @@ -221,14 +224,14 @@ func TestOracle_Request(t *testing.T) { require.Equal(t, vm.FaultState, aer[0].VMState) } t.Run("non-UTF8 url", func(t *testing.T) { - doBadRequest(t, cs.ScriptHash(), "\xff", nil, []byte{1, 2}, gasForResponse) + doBadRequest(t, cs.Hash, "\xff", nil, []byte{1, 2}, gasForResponse) }) t.Run("non-UTF8 filter", func(t *testing.T) { var f = "\xff" - doBadRequest(t, cs.ScriptHash(), "url", &f, []byte{1, 2}, gasForResponse) + doBadRequest(t, cs.Hash, "url", &f, []byte{1, 2}, gasForResponse) }) t.Run("not enough gas", func(t *testing.T) { - doBadRequest(t, cs.ScriptHash(), "url", nil, nil, 1000) + doBadRequest(t, cs.Hash, "url", nil, nil, 1000) }) }) } diff --git a/pkg/core/state/contract.go b/pkg/core/state/contract.go index ea9570b38..f616d4d79 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -1,80 +1,50 @@ 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/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" ) // Contract holds information about a smart contract in the NEO blockchain. type Contract struct { - ID int32 - Script []byte - Manifest manifest.Manifest - - scriptHash util.Uint160 + ID int32 `json:"id"` + UpdateCounter uint16 `json:"updatecounter"` + Hash util.Uint160 `json:"hash"` + Script []byte `json:"script"` + Manifest manifest.Manifest `json:"manifest"` } // DecodeBinary implements Serializable interface. func (cs *Contract) DecodeBinary(br *io.BinReader) { cs.ID = int32(br.ReadU32LE()) + cs.UpdateCounter = br.ReadU16LE() + cs.Hash.DecodeBinary(br) cs.Script = br.ReadVarBytes() cs.Manifest.DecodeBinary(br) - cs.createHash() } // EncodeBinary implements Serializable interface. func (cs *Contract) EncodeBinary(bw *io.BinWriter) { bw.WriteU32LE(uint32(cs.ID)) + bw.WriteU16LE(cs.UpdateCounter) + cs.Hash.EncodeBinary(bw) bw.WriteVarBytes(cs.Script) cs.Manifest.EncodeBinary(bw) } -// ScriptHash returns a contract script hash. -func (cs *Contract) ScriptHash() util.Uint160 { - if cs.scriptHash.Equals(util.Uint160{}) { - cs.createHash() +// CreateContractHash creates deployed contract hash from transaction sender +// and contract script. +func CreateContractHash(sender util.Uint160, script []byte) util.Uint160 { + w := io.NewBufBinWriter() + emit.Opcodes(w.BinWriter, opcode.ABORT) + emit.Bytes(w.BinWriter, sender.BytesBE()) + emit.Bytes(w.BinWriter, script) + if w.Err != nil { + panic(w.Err) } - return cs.scriptHash -} - -// createHash creates contract script hash. -func (cs *Contract) createHash() { - cs.scriptHash = hash.Hash160(cs.Script) -} - -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 + return hash.Hash160(w.Bytes()) } diff --git a/pkg/core/state/contract_test.go b/pkg/core/state/contract_test.go index 92dfcb0f6..5283b5acc 100644 --- a/pkg/core/state/contract_test.go +++ b/pkg/core/state/contract_test.go @@ -7,7 +7,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" - "github.com/stretchr/testify/assert" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" ) func TestEncodeDecodeContractState(t *testing.T) { @@ -30,21 +31,30 @@ func TestEncodeDecodeContractState(t *testing.T) { ReturnType: smartcontract.BoolType, }} contract := &Contract{ - ID: 123, - Script: script, - Manifest: *m, + ID: 123, + UpdateCounter: 42, + Hash: h, + Script: script, + Manifest: *m, } - 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()) }) } + +func TestCreateContractHash(t *testing.T) { + var script = []byte{1, 2, 3} + var sender util.Uint160 + var err error + + require.Equal(t, "b4b7417195feca1cdb5a99504ab641d8c220ae99", CreateContractHash(sender, script).StringLE()) + sender, err = util.Uint160DecodeStringLE("a400ff00ff00ff00ff00ff00ff00ff00ff00ff01") + require.NoError(t, err) + require.Equal(t, "e56e4ee87f89a70e9138432c387ad49f2ee5b55f", CreateContractHash(sender, script).StringLE()) +} diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index c9042a468..628f342a2 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -121,12 +121,12 @@ func (c *Client) Init() error { if err != nil { return fmt.Errorf("failed to get NEO contract scripthash: %w", err) } - c.cache.nativeHashes["neo"] = neoContractHash.ScriptHash() + c.cache.nativeHashes["neo"] = neoContractHash.Hash gasContractHash, err := c.GetContractStateByAddressOrName("gas") if err != nil { return fmt.Errorf("failed to get GAS contract scripthash: %w", err) } - c.cache.nativeHashes["gas"] = gasContractHash.ScriptHash() + c.cache.nativeHashes["gas"] = gasContractHash.Hash c.initDone = true return nil } diff --git a/pkg/rpc/client/nep17.go b/pkg/rpc/client/nep17.go index 3d68664dd..941cd908f 100644 --- a/pkg/rpc/client/nep17.go +++ b/pkg/rpc/client/nep17.go @@ -124,8 +124,12 @@ func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, reci recipients[i].Address, recipients[i].Amount, nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) } + accAddr, err := address.StringToUint160(acc.Address) + if err != nil { + return nil, fmt.Errorf("bad account address: %v", err) + } return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, transaction.Signer{ - Account: acc.Contract.ScriptHash(), + Account: accAddr, Scopes: transaction.CalledByEntry, }) } diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 8111e49fd..22ffeec8a 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -622,6 +622,6 @@ func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) { if err != nil { return util.Uint160{}, err } - c.cache.nativeHashes[lowercasedName] = cs.ScriptHash() - return cs.ScriptHash(), nil + c.cache.nativeHashes[lowercasedName] = cs.Hash + return cs.Hash, nil } diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 1d414f6b9..b3c48eba3 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -19,6 +19,7 @@ import ( "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/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/request" @@ -335,10 +336,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ m := manifest.NewManifest("Test") cs := &state.Contract{ ID: 0, + Hash: hash.Hash160(script), Script: script, Manifest: *m, } - _ = cs.ScriptHash() return cs }, }, @@ -356,10 +357,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ m := manifest.NewManifest("Test") cs := &state.Contract{ ID: 0, + Hash: hash.Hash160(script), Script: script, Manifest: *m, } - _ = cs.ScriptHash() return cs }, }, @@ -377,10 +378,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ m := manifest.NewManifest("Test") cs := &state.Contract{ ID: 0, + Hash: hash.Hash160(script), Script: script, Manifest: *m, } - _ = cs.ScriptHash() return cs }, }, diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 1d6008eca..20db7ca7e 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -56,11 +56,11 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "55b692ecc09f240355e042c6c07e8f3fe57546b1" -const deploymentTxHash = "99e40e5d169eb9a2b6faebc6fc596c050cf3f8a70ad25de8f44309bc8ccbfbfb" +const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" +const deploymentTxHash = "8e848d367d6194c0ddc12310e0509c0bb3bf716b0bcd9c10508b4bc9c954408d" const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" -const verifyContractHash = "c1213693b22cb0454a436d6e0bd76b8c0a3bfdf7" +const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" const testVerifyContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGgRVUH4J+yMIaonBwAAABFADBQNDwMCCQACAQMHAwQFAgEADgYMCdswcWkRVUH4J+yMIaonBwAAABJAE0A=" @@ -142,7 +142,7 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, cs interface{}) { res, ok := cs.(*state.Contract) require.True(t, ok) - assert.Equal(t, testContractHash, res.ScriptHash().StringLE()) + assert.Equal(t, testContractHash, res.Hash.StringLE()) }, }, { @@ -1345,7 +1345,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "80009744770", + Amount: "80009690770", LastUpdated: 7, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 1a29ce8cc5670425fdeacdb7ee190458c1ce33a5..4023d6e32da3940d2fd7013f4f0535a78d608a16 100644 GIT binary patch delta 2940 zcmbW3S6CC+7KM{g5+KpQ&%oO4qN8}7KgEa6U9TSF2{URfBEJWqz0i(iS2=!^A{-||)w~XPc*NQWU_9(A z(th(!f6hQeo1)}tWG)nA7J4G*(aXDwVegG^qcqkmPVU^%_VwlMf{shL`*aCh?Z zdI(PCA|Dh3o$oWL?m=U5YM}+qjp>r6)odwmfzJ*Pd^uF!K`*Z@&s+mq&FS%-ZDVq$ z;{Hk>kh&7XEf1K<#1DV#`e8n`Cse^<&V6-0^Vxv*HlqavYOJW#F#v#F&IP;45smb7 z^Yuh(BdtQ+eUM5>H+L_jb~Mu0Gvu;AF&L@sL{;UbQ+pu1)Kwl6sx%L-ninDjq!M}J zsAIgTmI#mq&Pc=0(_i_L|A7bpb8-CJNu^QfjNTnmYo|WbmIIaFx{&_dHq;1f&93(1 z8v$aJm7A!*C03w!MzfZ)Sjw5X!i@>dS7MuSdO|(Nb23heba-_~sgV~YZ_T0jRl40S z6whQYeQ+$;c|gm>L?YIyu`qR<^ZSTw7X}# zD_3}}6qePxcb#KN%JO<>V&i35D=zJJvMJ?1u1v7s{C+b~H(=7_w_3}B|DoAm_2KMj zx_h}GZ{4ISIrD13wwBYpPD?n)$PW;?-ehRI1DtK@ZH0=t7E^1><+pS)S8J<&n^q8G z55+7-ac*rA$D`Fpj(rUD_^yN)Q_z&oY zVBN1dSpoH-m|eRM$tZ7RWY*xbu9Hl6y11(6in(Soo?4S3B&HLTSYtAHuW?N}jBrTZ zjU4@;-}lILOUAW%(NAh@kC~}YjeDvqnz!%w*k0{?yAAJmVcK>M{+1sz3@)09E25L! z9HPOSKR-9avLBJV43jD3yN}us>`GDvit}s7kJ6Rt!Q_ZyoEzvi`^Un8`h{?5a%X`3 z3EAsF)R>}_ZEuoy_SfReGMO~QWXbe)I$Yv;-8-6UXGN9c{2Oi&PDye4wz)-6p~Qk) z`!>lqL9$t@3X0)aU72fYGaG-hF&kkzh|%YcWb@4&O;Q|)(w%{zWJ&NQ_n#o4q>*$^ z!fOpgMat%VV_sKiqohVm7yHT1vmrfedScZK8A<5_jHN5pLw89B9s~< zD$C&jZ>8D_IAM0v-)@{_$}5%l|5+F2R(fK9TmBh%;$p@Z@8%@ciu4BgIC)BAe_+yV z`1MZFeoOn$cgID}=@~N!@1Q)V$+P%}YW1@m#%T0!j6DLercN3V4zs4Zf!5HlQ zq_7$nXNbM<2fu092#7+uA=@HR)F9wfurVzqnL^x!b)UrK^~mL-DoX>uCS$YOC>n1K zVj`I)~+x_<5V}Crz7NVOo9PA(mcRK9&l$@1JYQ4MoosZPmt4CJ`P( zD|K}*fL!ykl_iVfFMk^zCLZclpT-SZ6+$sw#t}aWyg>tt1trHFu*1q@;m%)9d*}rw zhqj#pE2VL3MYwjHVVaf5)eP<$;2qMcgAzjAIjo;Nab%?3mMf5)Ci5NzFl6_bj~PkI`ikS5r{&MRdwTZN;MsU!YntgTFsrljO7GcQ2=ypR3pN z6K13xR}!Me3Y`Z1&Dn7w3GzQ7C)f&wgao{Iz>(<-YaxWM>E zFP2fQWM}oBcE(mNa&rsP$8M87SuICFrTnqt z_UaGF!=%X6hUwx79}l!NjhMxaSvtUSSy-AJD=e8OHdDS40>5VylJO|zl1p*UrG})D z�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 diff --git a/pkg/wallet/wallet.go b/pkg/wallet/wallet.go index 3b1fc236b..4b8bf364e 100644 --- a/pkg/wallet/wallet.go +++ b/pkg/wallet/wallet.go @@ -7,6 +7,7 @@ import ( "os" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" ) @@ -181,8 +182,9 @@ func (w *Wallet) Close() { // GetAccount returns account corresponding to the provided scripthash. func (w *Wallet) GetAccount(h util.Uint160) *Account { + addr := address.Uint160ToString(h) for _, acc := range w.Accounts { - if c := acc.Contract; c != nil && h.Equals(c.ScriptHash()) { + if acc.Address == addr { return acc } } diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go index 302795845..1ce1302ce 100644 --- a/pkg/wallet/wallet_test.go +++ b/pkg/wallet/wallet_test.go @@ -167,6 +167,7 @@ func TestWallet_GetAccount(t *testing.T) { } for _, acc := range accounts { + acc.Address = address.Uint160ToString(acc.Contract.ScriptHash()) wallet.AddAccount(acc) } diff --git a/scripts/gendump/main.go b/scripts/gendump/main.go index 1ce76be2c..3c4f90496 100644 --- a/scripts/gendump/main.go +++ b/scripts/gendump/main.go @@ -18,7 +18,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native" "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/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -75,18 +74,13 @@ func main() { handleError("can't tranfser GAS", err) lastBlock = addBlock(bc, lastBlock, valScript, txMoveNeo, txMoveGas) - tx, avm, err := testchain.NewDeployTx("DumpContract", strings.NewReader(contract)) + tx, contractHash, err := testchain.NewDeployTx("DumpContract", h, strings.NewReader(contract)) handleError("can't create deploy tx", err) - tx.Signers = []transaction.Signer{{ - Account: h, - Scopes: transaction.CalledByEntry, - }} tx.NetworkFee = 10_000_000 tx.ValidUntilBlock = bc.BlockHeight() + 1 handleError("can't sign deploy tx", acc.SignTx(tx)) lastBlock = addBlock(bc, lastBlock, valScript, tx) - contractHash := hash.Hash160(avm) key := make([]byte, 10) value := make([]byte, 10) nonce := uint32(0) From 0c7e727859f3c77c15068a91cbfaf6223d0fe77a Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 26 Nov 2020 21:31:24 +0300 Subject: [PATCH 05/11] nef: drop scripthash It's no longer a part of the file. --- cli/testdata/verify.nef | Bin 86 -> 66 bytes pkg/rpc/server/server_test.go | 4 ++-- pkg/rpc/server/testdata/testblocks.acc | Bin 7530 -> 7490 bytes pkg/smartcontract/nef/nef.go | 20 +++++--------------- pkg/smartcontract/nef/nef_test.go | 14 -------------- 5 files changed, 7 insertions(+), 31 deletions(-) diff --git a/cli/testdata/verify.nef b/cli/testdata/verify.nef index 309796612c518d21dc13587970bab29636f5642b..2d3a78cabc30f15fa95ff4cb0ebc05abf2b6cdb1 100755 GIT binary patch delta 31 ecmWG@nxG*S!+-z@UK4I{hBGn}*7w}|@_C;ImU=1f*GqnIn=_n|LC_(bf!P57 DN>vTE diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 20db7ca7e..ff55d6b83 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -57,7 +57,7 @@ type rpcTestCase struct { } const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" -const deploymentTxHash = "8e848d367d6194c0ddc12310e0509c0bb3bf716b0bcd9c10508b4bc9c954408d" +const deploymentTxHash = "f4abbf6a5af7b79819378cce1576dd72663a95961010383d67d8c4e1f00fac02" const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" @@ -1345,7 +1345,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "80009690770", + Amount: "80009730770", LastUpdated: 7, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 4023d6e32da3940d2fd7013f4f0535a78d608a16..167c4ab35b358e46cdf0ecacb10f12b01f6af994 100644 GIT binary patch delta 2068 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 Date: Thu, 26 Nov 2020 22:45:51 +0300 Subject: [PATCH 06/11] core: fix contract-based verification script hash When using contract-based verification it's important to load contract's hash along with the script, otherwise it won't be valid. Simplify things along the way. --- pkg/core/blockchain.go | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index b07945fe7..aceabd0a7 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1601,18 +1601,15 @@ var ( // initVerificationVM initializes VM for witness check. func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, witness *transaction.Witness) error { - var offset int - var isNative bool - var initMD *manifest.Method - verification := witness.VerificationScript - flags := smartcontract.NoneFlag - if len(verification) != 0 { + v := ic.VM + if len(witness.VerificationScript) != 0 { if witness.ScriptHash() != hash { return ErrWitnessHashMismatch } if bc.contracts.ByHash(hash) != nil { return ErrNativeContractWitness } + v.LoadScriptWithFlags(witness.VerificationScript, smartcontract.NoneFlag) } else { cs, err := ic.DAO.GetContractState(hash) if err != nil { @@ -1622,26 +1619,21 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, if md == nil { return ErrInvalidVerificationContract } - verification = cs.Script - offset = md.Offset - initMD = cs.Manifest.ABI.GetMethod(manifest.MethodInit) - isNative = cs.ID < 0 - flags = smartcontract.AllowStates - } + initMD := cs.Manifest.ABI.GetMethod(manifest.MethodInit) + v.LoadScriptWithHash(cs.Script, hash, smartcontract.AllowStates) + v.Jump(v.Context(), md.Offset) - v := ic.VM - v.LoadScriptWithFlags(verification, flags) - v.Jump(v.Context(), offset) - if isNative { - w := io.NewBufBinWriter() - emit.Opcodes(w.BinWriter, opcode.DEPTH, opcode.PACK) - emit.String(w.BinWriter, manifest.MethodVerify) - if w.Err != nil { - return w.Err + if cs.ID < 0 { + w := io.NewBufBinWriter() + emit.Opcodes(w.BinWriter, opcode.DEPTH, opcode.PACK) + emit.String(w.BinWriter, manifest.MethodVerify) + if w.Err != nil { + return w.Err + } + v.LoadScript(w.Bytes()) + } else if initMD != nil { + v.Call(v.Context(), initMD.Offset) } - v.LoadScript(w.Bytes()) - } else if initMD != nil { - v.Call(v.Context(), initMD.Offset) } v.LoadScript(witness.InvocationScript) return nil From d93aa745bb7c60720fd14405d8ae09fa5f88cbda Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 26 Nov 2020 23:02:00 +0300 Subject: [PATCH 07/11] contract: avoid going to the DB for entry scripts This optimizes out DB access for non-deployed contracts under the assumption that deployed ones are always loaded via `LoadScriptWithHash` (and if they're not --- it's a bug anyway with the new hashing model) which actually is a very popular case (every entry script does that). --- pkg/core/interop/contract/call.go | 11 +++++++---- pkg/core/interop_system_test.go | 2 +- pkg/vm/context.go | 8 ++++++++ pkg/vm/vm.go | 8 ++++++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index 08f064c37..1a327e608 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -46,10 +46,13 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem if strings.HasPrefix(name, "_") { return errors.New("invalid method name (starts with '_')") } - curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) - if err == nil { - if !curr.Manifest.CanCall(u, &cs.Manifest, name) { - return errors.New("disallowed method call") + ctx := ic.VM.Context() + if ctx != nil && ctx.IsDeployed() { + curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) + if err == nil { + if !curr.Manifest.CanCall(u, &cs.Manifest, name) { + return errors.New("disallowed method call") + } } } return CallExInternal(ic, cs, name, args, f, vm.EnsureNotEmpty, nil) diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 11e80b7e2..b0f0c1e62 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -581,7 +581,7 @@ func TestContractCall(t *testing.T) { runInvalid := func(args ...interface{}) func(t *testing.T) { return func(t *testing.T) { - loadScript(ic, currScript, 42) + loadScriptWithHashAndFlags(ic, currScript, h, smartcontract.All, 42) for i := range args { ic.VM.Estack().PushVal(args[i]) } diff --git a/pkg/vm/context.go b/pkg/vm/context.go index 08d38b87a..85d6e2611 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -42,6 +42,9 @@ type Context struct { // Caller's contract script hash. callingScriptHash util.Uint160 + // Set to true when running deployed contracts. + isDeployed bool + // Call flags this context was created with. callFlag smartcontract.CallFlag @@ -256,6 +259,11 @@ func (c *Context) String() string { return "execution context" } +// IsDeployed returns whether this context contains deployed contract. +func (c *Context) IsDeployed() bool { + return c.isDeployed +} + // getContextScriptHash returns script hash of the invocation stack element // number n. func (v *VM) getContextScriptHash(n int) util.Uint160 { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 03190378e..9fd3d4540 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -284,12 +284,16 @@ func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) { } // LoadScriptWithHash if similar to the LoadScriptWithFlags 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. +// given script hash directly into the Context to avoid its recalculations and to make +// is possible to override it for deployed contracts with special hashes (the function +// assumes that it is used for deployed contracts setting context's parameters +// accordingly). 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, f smartcontract.CallFlag) { shash := v.GetCurrentScriptHash() v.LoadScriptWithFlags(b, f) ctx := v.Context() + ctx.isDeployed = true ctx.scriptHash = hash ctx.callingScriptHash = shash } From e12c52f588c2cb9f7c271e5ad96294e48d0e5f04 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 27 Nov 2020 19:33:36 +0300 Subject: [PATCH 08/11] nef: change checksum calculation scheme It's now being calculated for whole file, not just header. --- cli/testdata/verify.nef | Bin 66 -> 66 bytes pkg/rpc/server/server_test.go | 2 +- pkg/rpc/server/testdata/testblocks.acc | Bin 7490 -> 7490 bytes pkg/smartcontract/nef/nef.go | 33 ++++++++++++------------- pkg/smartcontract/nef/nef_test.go | 8 +++--- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/cli/testdata/verify.nef b/cli/testdata/verify.nef index 2d3a78cabc30f15fa95ff4cb0ebc05abf2b6cdb1..f8bf2ff484af657cb2466fb260319611cd3feac5 100755 GIT binary patch delta 19 acmZ>Anqb1m8P3Qc=n&4p?BF1?VI2S_iUeH% delta 19 acmZ>Anqb10;5FeEXE-B+phGwVvjYG(7zDQf diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index ff55d6b83..1c203176a 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -57,7 +57,7 @@ type rpcTestCase struct { } const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" -const deploymentTxHash = "f4abbf6a5af7b79819378cce1576dd72663a95961010383d67d8c4e1f00fac02" +const deploymentTxHash = "3f81bc99525ed4b1cbb4a3535feadf73176db646a2879aaf975737348a425edc" const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 167c4ab35b358e46cdf0ecacb10f12b01f6af994..999cb641fef7cc683c425d11b10ee86486fc93ae 100644 GIT binary patch delta 2069 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 EDrVBucK`qY diff --git a/pkg/smartcontract/nef/nef.go b/pkg/smartcontract/nef/nef.go index c65e061be..225f0ad86 100644 --- a/pkg/smartcontract/nef/nef.go +++ b/pkg/smartcontract/nef/nef.go @@ -23,10 +23,10 @@ import ( // | Compiler | 32 bytes | Compiler used | // | Version | 16 bytes | Compiler version (Major, Minor, Build, Version) | // +------------+-----------+------------------------------------------------------------+ -// | Checksum | 4 bytes | First four bytes of double SHA256 hash of the header | -// +------------+-----------+------------------------------------------------------------+ // | Script | Var bytes | Var bytes for the payload | // +------------+-----------+------------------------------------------------------------+ +// | Checksum | 4 bytes | First four bytes of double SHA256 hash of the header | +// +------------+-----------+------------------------------------------------------------+ const ( // Magic is a magic File header constant. @@ -40,8 +40,8 @@ const ( // File represents compiled contract file structure according to the NEF3 standard. type File struct { Header Header - Checksum uint32 Script []byte + Checksum uint32 } // Header represents File header. @@ -73,7 +73,7 @@ func NewFile(script []byte) (File, error) { return file, err } file.Header.Version = v - file.Checksum = file.Header.CalculateChecksum() + file.Checksum = file.CalculateChecksum() return file, nil } @@ -171,36 +171,35 @@ func (h *Header) DecodeBinary(r *io.BinReader) { } // CalculateChecksum returns first 4 bytes of double-SHA256(Header) converted to uint32. -func (h *Header) CalculateChecksum() uint32 { - buf := io.NewBufBinWriter() - h.EncodeBinary(buf.BinWriter) - if buf.Err != nil { - panic(buf.Err) +func (n *File) CalculateChecksum() uint32 { + bb, err := n.Bytes() + if err != nil { + panic(err) } - return binary.LittleEndian.Uint32(hash.Checksum(buf.Bytes())) + return binary.LittleEndian.Uint32(hash.Checksum(bb[:len(bb)-4])) } // EncodeBinary implements io.Serializable interface. func (n *File) EncodeBinary(w *io.BinWriter) { n.Header.EncodeBinary(w) - w.WriteU32LE(n.Checksum) w.WriteVarBytes(n.Script) + w.WriteU32LE(n.Checksum) } // DecodeBinary implements io.Serializable interface. func (n *File) DecodeBinary(r *io.BinReader) { n.Header.DecodeBinary(r) - n.Checksum = r.ReadU32LE() - checksum := n.Header.CalculateChecksum() - if checksum != n.Checksum { - r.Err = errors.New("CRC verification fail") - return - } n.Script = r.ReadVarBytes(MaxScriptLength) if len(n.Script) == 0 { r.Err = errors.New("empty script") return } + n.Checksum = r.ReadU32LE() + checksum := n.CalculateChecksum() + if checksum != n.Checksum { + r.Err = errors.New("checksum verification failure") + return + } } // Bytes returns byte array with serialized NEF File. diff --git a/pkg/smartcontract/nef/nef_test.go b/pkg/smartcontract/nef/nef_test.go index 1ed9575a3..d64897f77 100644 --- a/pkg/smartcontract/nef/nef_test.go +++ b/pkg/smartcontract/nef/nef_test.go @@ -36,20 +36,20 @@ func TestEncodeDecodeBinary(t *testing.T) { t.Run("zero-length script", func(t *testing.T) { expected.Script = make([]byte, 0) - expected.Checksum = expected.Header.CalculateChecksum() + expected.Checksum = expected.CalculateChecksum() checkDecodeError(t, expected) }) t.Run("invalid script length", func(t *testing.T) { newScript := make([]byte, MaxScriptLength+1) expected.Script = newScript - expected.Checksum = expected.Header.CalculateChecksum() + expected.Checksum = expected.CalculateChecksum() checkDecodeError(t, expected) }) t.Run("positive", func(t *testing.T) { expected.Script = script - expected.Checksum = expected.Header.CalculateChecksum() + expected.Checksum = expected.CalculateChecksum() expected.Header.Magic = Magic testserdes.EncodeDecodeBinary(t, expected, &File{}) }) @@ -76,7 +76,7 @@ func TestBytesFromBytes(t *testing.T) { }, Script: script, } - expected.Checksum = expected.Header.CalculateChecksum() + expected.Checksum = expected.CalculateChecksum() bytes, err := expected.Bytes() require.NoError(t, err) From 4d0eaef5101489c1538a7e5c82d29b13b56cc894 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 27 Nov 2020 21:42:54 +0300 Subject: [PATCH 09/11] nef: treat Version as string Following changes in C# code and simpilifying things a lot. --- pkg/smartcontract/nef/nef.go | 110 +++++++----------------------- pkg/smartcontract/nef/nef_test.go | 90 +----------------------- 2 files changed, 25 insertions(+), 175 deletions(-) diff --git a/pkg/smartcontract/nef/nef.go b/pkg/smartcontract/nef/nef.go index 225f0ad86..e0a172b0d 100644 --- a/pkg/smartcontract/nef/nef.go +++ b/pkg/smartcontract/nef/nef.go @@ -4,9 +4,6 @@ import ( "bytes" "encoding/binary" "errors" - "fmt" - "strconv" - "strings" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" @@ -21,7 +18,7 @@ import ( // +------------+-----------+------------------------------------------------------------+ // | Magic | 4 bytes | Magic header | // | Compiler | 32 bytes | Compiler used | -// | Version | 16 bytes | Compiler version (Major, Minor, Build, Version) | +// | Version | 16 bytes | Compiler version | // +------------+-----------+------------------------------------------------------------+ // | Script | Var bytes | Var bytes for the payload | // +------------+-----------+------------------------------------------------------------+ @@ -35,6 +32,8 @@ const ( MaxScriptLength = 1024 * 1024 // compilerFieldSize is the length of `Compiler` File header field in bytes. compilerFieldSize = 32 + // versionFieldSize is the length of `Version` File header field in bytes. + versionFieldSize = 16 ) // File represents compiled contract file structure according to the NEF3 standard. @@ -48,97 +47,26 @@ type File struct { type Header struct { Magic uint32 Compiler string - Version Version -} - -// Version represents compiler version. -type Version struct { - Major int32 - Minor int32 - Build int32 - Revision int32 + Version string } // NewFile returns new NEF3 file with script specified. -func NewFile(script []byte) (File, error) { - file := File{ +func NewFile(script []byte) (*File, error) { + file := &File{ Header: Header{ Magic: Magic, Compiler: "neo-go", + Version: config.Version, }, Script: script, } - v, err := GetVersion(config.Version) - if err != nil { - return file, err + if len(config.Version) > versionFieldSize { + return nil, errors.New("too long version") } - file.Header.Version = v file.Checksum = file.CalculateChecksum() return file, nil } -// GetVersion returns Version from the given string. It accepts the following formats: -// `major[-...].minor[-...].build[-...]` and `major[-...].minor[-...].build[-...].revision[-...]` -// where `major`, `minor`, `build` and `revision` are 32-bit integers with base=10 -func GetVersion(version string) (Version, error) { - var ( - result Version - err error - ) - versions := strings.SplitN(version, ".", 4) - if len(versions) < 3 { - return result, errors.New("invalid version format") - } - result.Major, err = parseDashedVersion(versions[0]) - if err != nil { - return result, fmt.Errorf("failed to parse major version: %w", err) - } - result.Minor, err = parseDashedVersion(versions[1]) - if err != nil { - return result, fmt.Errorf("failed to parse minor version: %w", err) - - } - result.Build, err = parseDashedVersion(versions[2]) - if err != nil { - return result, fmt.Errorf("failed to parse build version: %w", err) - } - if len(versions) == 4 { - result.Revision, err = parseDashedVersion(versions[3]) - if err != nil { - return result, fmt.Errorf("failed to parse revision version: %w", err) - } - } - - return result, nil -} - -// parseDashedVersion extracts int from string of the format `int[-...]` where `int` is -// a 32-bit integer with base=10. -func parseDashedVersion(version string) (int32, error) { - version = strings.SplitN(version, "-", 2)[0] - result, err := strconv.ParseInt(version, 10, 32) - if err != nil { - return 0, err - } - return int32(result), nil -} - -// EncodeBinary implements io.Serializable interface. -func (v *Version) EncodeBinary(w *io.BinWriter) { - w.WriteU32LE(uint32(v.Major)) - w.WriteU32LE(uint32(v.Minor)) - w.WriteU32LE(uint32(v.Build)) - w.WriteU32LE(uint32(v.Revision)) -} - -// DecodeBinary implements io.Serializable interface. -func (v *Version) DecodeBinary(r *io.BinReader) { - v.Major = int32(r.ReadU32LE()) - v.Minor = int32(r.ReadU32LE()) - v.Build = int32(r.ReadU32LE()) - v.Revision = int32(r.ReadU32LE()) -} - // EncodeBinary implements io.Serializable interface. func (h *Header) EncodeBinary(w *io.BinWriter) { w.WriteU32LE(h.Magic) @@ -146,12 +74,15 @@ func (h *Header) EncodeBinary(w *io.BinWriter) { w.Err = errors.New("invalid compiler name length") return } - bytes := []byte(h.Compiler) - w.WriteBytes(bytes) - if len(bytes) < compilerFieldSize { - w.WriteBytes(make([]byte, compilerFieldSize-len(bytes))) + var b = make([]byte, compilerFieldSize) + copy(b, []byte(h.Compiler)) + w.WriteBytes(b) + b = b[:versionFieldSize] + for i := range b { + b[i] = 0 } - h.Version.EncodeBinary(w) + copy(b, []byte(h.Version)) + w.WriteBytes(b) } // DecodeBinary implements io.Serializable interface. @@ -167,7 +98,12 @@ func (h *Header) DecodeBinary(r *io.BinReader) { return r == 0 }) h.Compiler = string(buf) - h.Version.DecodeBinary(r) + buf = buf[:versionFieldSize] + r.ReadBytes(buf) + buf = bytes.TrimRightFunc(buf, func(r rune) bool { + return r == 0 + }) + h.Version = string(buf) } // CalculateChecksum returns first 4 bytes of double-SHA256(Header) converted to uint32. diff --git a/pkg/smartcontract/nef/nef_test.go b/pkg/smartcontract/nef/nef_test.go index d64897f77..f5236fd64 100644 --- a/pkg/smartcontract/nef/nef_test.go +++ b/pkg/smartcontract/nef/nef_test.go @@ -13,12 +13,7 @@ func TestEncodeDecodeBinary(t *testing.T) { Header: Header{ Magic: Magic, Compiler: "the best compiler ever", - Version: Version{ - Major: 1, - Minor: 2, - Build: 3, - Revision: 4, - }, + Version: "1.2.3.4", }, Script: script, } @@ -67,12 +62,7 @@ func TestBytesFromBytes(t *testing.T) { Header: Header{ Magic: Magic, Compiler: "the best compiler ever", - Version: Version{ - Major: 1, - Minor: 2, - Build: 3, - Revision: 4, - }, + Version: "1.2.3.4", }, Script: script, } @@ -84,79 +74,3 @@ func TestBytesFromBytes(t *testing.T) { require.NoError(t, err) require.Equal(t, expected, actual) } - -func TestGetVersion(t *testing.T) { - testCases := map[string]struct { - input string - fails bool - expected Version - }{ - "major only": { - input: "1", - fails: true, - }, - "major and minor only": { - input: "1.1", - fails: true, - }, - "major, minor and revision only": { - input: "1.1.1", - expected: Version{ - Major: 1, - Minor: 1, - Build: 1, - Revision: 0, - }, - }, - "full version": { - input: "1.1.1.1", - expected: Version{ - Major: 1, - Minor: 1, - Build: 1, - Revision: 1, - }, - }, - "dashed, without revision": { - input: "1-pre.2-pre.3-pre", - expected: Version{ - Major: 1, - Minor: 2, - Build: 3, - Revision: 0, - }, - }, - "dashed, full version": { - input: "1-pre.2-pre.3-pre.4-pre", - expected: Version{ - Major: 1, - Minor: 2, - Build: 3, - Revision: 4, - }, - }, - "dashed build": { - input: "1.2.3-pre.4", - expected: Version{ - Major: 1, - Minor: 2, - Build: 3, - Revision: 4, - }, - }, - "extra versions": { - input: "1.2.3.4.5", - fails: true, - }, - } - for name, test := range testCases { - t.Run(name, func(t *testing.T) { - actual, err := GetVersion(test.input) - if test.fails { - require.NotNil(t, err) - } else { - require.Equal(t, test.expected, actual) - } - }) - } -} From 470e1592d9c320022d3e78304ab14b85a9f6a9c0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 27 Nov 2020 21:53:39 +0300 Subject: [PATCH 10/11] request: make CreateDeploymentScript work with NEFs And use it in testing code. --- cli/smartcontract/smart_contract.go | 2 +- internal/testchain/transaction.go | 18 +++--------------- pkg/rpc/request/txBuilder.go | 9 +++++++-- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index e8555d0b0..a556570de 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -767,7 +767,7 @@ func contractDeploy(ctx *cli.Context) error { return err } - txScript, err := request.CreateDeploymentScript(f, m) + txScript, err := request.CreateDeploymentScript(&nefFile, m) if err != nil { return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", err), 1) } diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index 02bf32791..c024ad85c 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -1,7 +1,6 @@ package testchain import ( - "encoding/json" gio "io" "github.com/nspcc-dev/neo-go/pkg/compiler" @@ -9,11 +8,11 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/fee" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -62,28 +61,17 @@ func NewDeployTx(name string, sender util.Uint160, r gio.Reader) (*transaction.T if err != nil { return nil, util.Uint160{}, err } - neb, err := ne.Bytes() - if err != nil { - return nil, util.Uint160{}, err - } m, err := di.ConvertToManifest(name, nil) if err != nil { return nil, util.Uint160{}, err } - bs, err := json.Marshal(m) + + txScript, err := request.CreateDeploymentScript(ne, m) if err != nil { return nil, util.Uint160{}, err } - w := io.NewBufBinWriter() - emit.Bytes(w.BinWriter, bs) - emit.Bytes(w.BinWriter, neb) - emit.Syscall(w.BinWriter, interopnames.SystemContractCreate) - if w.Err != nil { - return nil, util.Uint160{}, w.Err - } - txScript := w.Bytes() tx := transaction.New(Network(), txScript, 100*native.GASFactor) tx.Signers = []transaction.Signer{{Account: sender}} h := state.CreateContractHash(tx.Sender(), avm) diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index a096e99f2..a405d7de1 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "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" @@ -18,14 +19,18 @@ import ( // CreateDeploymentScript returns a script that deploys given smart contract // with its metadata. -func CreateDeploymentScript(avm []byte, manif *manifest.Manifest) ([]byte, error) { +func CreateDeploymentScript(ne *nef.File, manif *manifest.Manifest) ([]byte, error) { script := io.NewBufBinWriter() rawManifest, err := json.Marshal(manif) if err != nil { return nil, err } + neb, err := ne.Bytes() + if err != nil { + return nil, err + } emit.Bytes(script.BinWriter, rawManifest) - emit.Bytes(script.BinWriter, avm) + emit.Bytes(script.BinWriter, neb) emit.Syscall(script.BinWriter, interopnames.SystemContractCreate) return script.Bytes(), nil } From 167288712335e555c3dfc1f59f561250047e3888 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 30 Nov 2020 11:26:29 +0300 Subject: [PATCH 11/11] nef: increase version field to 32 bytes Follow recent C# changes. --- cli/testdata/verify.nef | Bin 66 -> 82 bytes pkg/rpc/server/server_test.go | 4 ++-- pkg/rpc/server/testdata/testblocks.acc | Bin 7490 -> 7522 bytes pkg/smartcontract/nef/nef.go | 11 ++++------- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/cli/testdata/verify.nef b/cli/testdata/verify.nef index f8bf2ff484af657cb2466fb260319611cd3feac5..5bb3c8bbe40518db36efb14d911e84b0a574ee9e 100755 GIT binary patch delta 51 zcmZm7st(zZH8v-%M6lbsIiqWmKhA$cakk+tSS4>SVFSIp`Mvrl#8OicA>wk zOV^Uz5L5Q?c)v#n?p~F=*B}Ae;)NZpVZ73ccfichdx0_&qlphl)XzQVn*}F1baeHh zI8TFgu(n{<-nF{BOZ{bHf3I7*7t)_Il>3B8Uu{wgM!)afx(r@5-xfn1mJ8wVje24P zu%*oNpIp7#q(ZHrGj&kh-O_kh+7UVHZ?DlG(4@JBq+PCVu%pKJ8Ib7GU|smavB++V z{=xX>XE(>2PSWgJcrR^G8vbSs)(Ua!BCjnkP|BA>?go9PHRWcc`I9XqHcoefb^6|` zn9%o6U8m*j!K>dpV#Zi3g9M!fYO?4us30Zq-QgA4x><>)czGoO)pev(U*fCwWd6+J zkoo)B7@i;y4GkEmhw&2`Xj)@j&P((K1*teblQwhXXDb<6P+zbYzb;-5%fZ?`FT(iC zhFd46DpRML?I24Q#T&DHSifp-^wS$s6s#f?r&0{c7~*W7b(>?W*-bIriF`1MiGR5X zS^hi}X|gvA={j=TiuUTV&EgSW$8(gweQrWXd^T%~zl0qAZm4yiIUb6erYqze*SR|% zhFcr_xePjPc)3exzfS%gy>gY{XeQeFwwH- zS{5EK;DHc<$Ve&4NXeiB2>t2qp1jK7315=L~mZN+u`9A-}Nlpa$(q0LEE_=#o#?1RSvl z5p{l==e1rTe_iQNES}hu)hn?>xsURC@;N|N;z;K-_A(-+1Fj+>($`cK)ijNdsBsCL zJdxf>Pqx^XqnL|%bD+2y58((An=8M~@ZOmV-no^tN!+z|D3tv+H8tuTNRC+0vt@Fm0!$jzAsUHK?tP~l0%Fh3;7ewI z7Ui}TbM-NQxpav=LbuShUA_Kw zI60(3%L$xzxSeu}r5YwgY;zOg3$r0Soy@d!Jsh}cIul3EZe#sMF89bmeI?Kl{oPD8yEAOXSx)hHh zHOzXad=11D5L*&^Ze?CnXnhG)Hoi^J3Jqq4;=CtaI>&MKE@2LS233AfZuXaXal#T< zFf#)YZ#L&zsX@-^_y_(9tS`)7$8B%6qX{VY#fp9QH5hEWXQrDXcWO4E8w4S5a=ZfR z?zhadx}YQ`Yo=JwL@|oL(`+j$Gp)$?ZCB)2PQNE)om5Gg@^aH+?V|{&r(Zs!Q=N#Rcw-s|A@Wx; zi$%=qVG_x+7rO;VOF{2%abYb?hN4iwlGnR+?KlvkO41H_ zgnGs}rXL)6=TfZ>6NPR`cRMXO;JhdGDIfCLQBO|&N zd zF0c29_CkieDR^daX_bQ#4iyiBiSA($Gz|Ar}y)MSBRJ=f3fzJQJh1qEkt64 z(P3VNPjxw9AbEVnaCa{8M@Y=y3O|ss_HZmDm{6xLphkKUH4zL?+=Xp81@tM&2S`A1 z$sWg>YX@`ZLv3jg!jggFk5q3dw)wtm>(@n=C+LMgz$YS9^7iFZxxaW=1&xzrwCj}( zJqC3ofj#9jURnu=ZYb`_nf(0Q&L6GC3+xq)owltp^CFWA0UXJf>?*k^c400yW2-X> zjDw3`oTcR5H5bUHh_L~GvApXM%?;}MNSCq`JoU6(Hr)S;-WTxW-vI^vKb-?b2q{C> zhNY7K+5Em_!pF0Dt8n&S(5F%9U}t;ShcD^-R=EW?qH7)y$+(j9v&!>gZz?mLK>l@C ZG|Z{u+Wp^*1J}rT_cDosFosi`{|6Fy-wyx) delta 2140 zcmYL}X*`q*8;1>p(LAzcopB5%V<}}x_J+)aWS2td7=(&S8eWXljD3CLl#WzmNmBNu zNHau?eI5Iftz>6NmP60a{q?@D|NXlrwCUQ3X3*sa5r>j2Q6!6=lY&n910Mo9 zWW`2l0+uFI`PFPVIV~f4>aH(`;+pZ^`q~edkV2O?BuCQ|xgljf!IfRHi}jvcdm}&F56z&z#WYC0aMu}~ zr6gJ9s9j(SRiX8s!KGv90Co`83@219&uIopX=cF2{F!4HVb4DL$%~+^nxu)n27x0X zz6qwra2*zVbr75{@TNDi{7mx+w_3PlZ78286D*WSDQ<*}a9DA}*nX$a^$2tJa&s3G za&J{bX<2LIN}nct;TpOtKelpQRJqhhx%A}6ZmBEAuXSeh3bqD%bOVGL85yh2A+kip z{)aLv`*;E%Y;5dc6+ePRHRe$pKFMRYqW{^E5+YOMYVzPhQE-*rL_XR4v015m)X$)C z8(w7}&~&CQo}e%|k7*hlOiNFX} zo@bE>y&as=#jlA-BtS%VhCWZ?DW&hs->IoG}_d8&RnKL z{1L=>VmW74Y`^)Fm2X00A@s>~^hJ@2=tlvzZ$cJ?RR+=eca|ibHlIJ@Qc^Sz?Q9&_>XVn?utuG3v z+6+$E)+bab%C3$ot8@^46@S}>l$(J*k}Oj*&XjSz7Q7PRbi=A z?Bf6S`eMWDf%w*(%}>!BvYk=4xAXux%8kxfmBMCs-37|#@=UxIC`1G;QoFA7QaZWi zHx?biJ=AhPTt&S#!9|_~C?}o!+4+cqY5bWDQF2A&7e|0S4Co+)g03Ij9x8+)b+b4M zY%_vuEdt%CNvA1ViPqB!mp>#@;wS89sSc zi!67&V0(G87{JbYDn@q@Un(KlkLQMI@LA3o7vx6W5u3=kkrxr_FWcC7Ki6y;sXYY5 zg!`O^JB$QquI|J~Gf+)pBGe_L67e7a8{PnEQ_1Y@?NdEm+@X*OD06lkS$^nQ5jGIN zaPA8pij1nE2^87Iz40Y&H#fFk_t9ulXi4cYrap`6>QnIn1vpc|MhLu^U6=+jky?*6 zG`+6+zKD0Af?>1U9eq0U`?=^G>#o7ticfyj`goH2MvXjsj=+71OR7&uJ)wbdl@pmG z*WGlbUK7%|u&n>;jO(|~mQ*dIDico(1Do{GAs{^OR`zNIvhMZMQbq|PJL_dT^7b13 zuI^rIYIc6Ol$&#?)9^{%#Aj==GByj-W@~5nb@l|DUTJ>g?S96YWlb~pC>XT@e?;^t zsY~mw#7$33rHq;zi%CO?A~7y`nVaz95*gxE(`|>w)QiG)+mSd~-fjT9O|MRpaml2- zZ6L^3e%SJ-9wQP7<#5SsDMgyR3}lM)y9~<(<*uWTm5+34?2X7E^lFZyop*vnD!*q0(q z%rVNc7fU5tzm8jEr7q)}rp7*=Hyo)5(SHqp6%0qPARhmBI9P<#x versionFieldSize { + if len(config.Version) > compilerFieldSize { return nil, errors.New("too long version") } file.Checksum = file.CalculateChecksum() @@ -77,7 +75,6 @@ func (h *Header) EncodeBinary(w *io.BinWriter) { var b = make([]byte, compilerFieldSize) copy(b, []byte(h.Compiler)) w.WriteBytes(b) - b = b[:versionFieldSize] for i := range b { b[i] = 0 } @@ -98,7 +95,7 @@ func (h *Header) DecodeBinary(r *io.BinReader) { return r == 0 }) h.Compiler = string(buf) - buf = buf[:versionFieldSize] + buf = buf[:compilerFieldSize] r.ReadBytes(buf) buf = bytes.TrimRightFunc(buf, func(r rune) bool { return r == 0