From 9c06ba6b07d2aa628fb9bc18063d0c533d1d8828 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 28 Jun 2021 19:09:19 +0300 Subject: [PATCH] smartcontract: marshal manifest `Extra` field as object, fix #2021 This is needed to strip whitespace which can be present. Signed-off-by: Evgeniy Stratonikov --- pkg/smartcontract/manifest/manifest.go | 30 ++++++++++++++++++--- pkg/smartcontract/manifest/manifest_test.go | 24 ++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 2efed7b23..8fde178dc 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -1,12 +1,14 @@ package manifest import ( + "bytes" "encoding/json" "errors" "math" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + ojson "github.com/virtuald/go-ordered-json" ) const ( @@ -140,10 +142,7 @@ func (m *Manifest) ToStackItem() (stackitem.Item, error) { } trusts = stackitem.Make(tItems) } - extra := stackitem.Make("null") - if m.Extra != nil { - extra = stackitem.NewByteArray(m.Extra) - } + extra := extraToStackItem(m.Extra) return stackitem.NewStruct([]stackitem.Item{ stackitem.Make(m.Name), stackitem.Make(groups), @@ -156,6 +155,29 @@ func (m *Manifest) ToStackItem() (stackitem.Item, error) { }), nil } +// extraToStackItem removes indentation from `Extra` field in JSON and +// converts it to a byte-array stack item. +func extraToStackItem(rawExtra []byte) stackitem.Item { + extra := stackitem.Make("null") + if rawExtra == nil || string(rawExtra) == "null" { + return extra + } + + d := ojson.NewDecoder(bytes.NewReader(rawExtra)) + // The result is put directly in the database and affects state-root calculation, + // thus use ordered map to stay compatible with C# implementation. + d.UseOrderedObject() + // Prevent accidental precision loss. + d.UseNumber() + + var obj interface{} + + // The error can't really occur because `json.RawMessage` is already a valid json. + _ = d.Decode(&obj) + res, _ := ojson.Marshal(obj) + return stackitem.NewByteArray(res) +} + // FromStackItem converts stackitem.Item to Manifest. func (m *Manifest) FromStackItem(item stackitem.Item) error { var err error diff --git a/pkg/smartcontract/manifest/manifest_test.go b/pkg/smartcontract/manifest/manifest_test.go index 844ec16ab..1af90a05f 100644 --- a/pkg/smartcontract/manifest/manifest_test.go +++ b/pkg/smartcontract/manifest/manifest_test.go @@ -289,7 +289,7 @@ func TestManifestToStackItem(t *testing.T) { }, }, }, - Extra: []byte(`even not a json allowed`), + Extra: []byte(`"even string allowed"`), } check(t, expected) }) @@ -395,3 +395,25 @@ func TestABI_FromStackItemErrors(t *testing.T) { }) } } + +func TestExtraToStackItem(t *testing.T) { + testCases := []struct { + raw, expected string + }{ + {"null", "null"}, + {"1", "1"}, + {"1.23456789101112131415", "1.23456789101112131415"}, + {`"string with space"`, `"string with space"`}, + {`{ "a":1, "sss" : {"m" : 1, "a" : 2} , "x" : 2 ,"c" :3,"z":4, "s":"5"}`, + `{"a":1,"sss":{"m":1,"a":2},"x":2,"c":3,"z":4,"s":"5"}`}, + {` [ 1, "array", { "d": "z", "a":"x", "c" : "y", "b":3}]`, + `[1,"array",{"d":"z","a":"x","c":"y","b":3}]`}, + } + + for _, tc := range testCases { + res := extraToStackItem([]byte(tc.raw)) + actual, ok := res.Value().([]byte) + require.True(t, ok) + require.Equal(t, tc.expected, string(actual)) + } +}