diff --git a/cli/nep11_test.go b/cli/nep11_test.go index a1d967cc9..519f152ce 100644 --- a/cli/nep11_test.go +++ b/cli/nep11_test.go @@ -312,7 +312,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { ScriptHash: verifyH, Name: "OnNEP11Payment", Item: stackitem.NewArray([]stackitem.Item{ - stackitem.NewByteArray(nftOwnerHash.BytesBE()), + stackitem.NewBuffer(nftOwnerHash.BytesBE()), stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewByteArray(tokenID1), stackitem.NewByteArray([]byte("some_data")), 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)) + } +} diff --git a/pkg/vm/stackitem/serialization.go b/pkg/vm/stackitem/serialization.go index 3a40f4590..b30cb02c1 100644 --- a/pkg/vm/stackitem/serialization.go +++ b/pkg/vm/stackitem/serialization.go @@ -129,7 +129,10 @@ func decodeBinaryStackItem(r *io.BinReader, allowInvalid bool) Item { switch t { case ByteArrayT, BufferT: data := r.ReadVarBytes(MaxSize) - return NewByteArray(data) + if t == ByteArrayT { + return NewByteArray(data) + } + return NewBuffer(data) case BooleanT: var b = r.ReadBool() return NewBool(b)