diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index e5a7a5519..c47a97283 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -29,7 +29,7 @@ import ( // Tuning parameters. const ( headerBatchCount = 2000 - version = "0.0.8" + version = "0.0.9" // This one comes from C# code and it's different from the constant used // when creating an asset with Neo.Asset.Create interop call. It looks @@ -708,7 +708,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { Trigger: trigger.Application, VMState: v.State(), GasConsumed: v.GasConsumed(), - Stack: v.Stack("estack"), + Stack: v.Estack().ToContractParameters(), Events: systemInterop.notifications, } err = cache.PutAppExecResult(aer) diff --git a/pkg/core/dao_test.go b/pkg/core/dao_test.go index 94125ecd7..b49296658 100644 --- a/pkg/core/dao_test.go +++ b/pkg/core/dao_test.go @@ -171,7 +171,11 @@ func TestGetValidators(t *testing.T) { func TestPutGetAppExecResult(t *testing.T) { dao := newDao(storage.NewMemoryStore()) hash := random.Uint256() - appExecResult := &state.AppExecResult{TxHash: hash, Events: []state.NotificationEvent{}} + appExecResult := &state.AppExecResult{ + TxHash: hash, + Events: []state.NotificationEvent{}, + Stack: []smartcontract.Parameter{}, + } err := dao.PutAppExecResult(appExecResult) require.NoError(t, err) gotAppExecResult, err := dao.GetAppExecResult(hash) diff --git a/pkg/core/state/notification_event.go b/pkg/core/state/notification_event.go index 24e6659e8..2599f781e 100644 --- a/pkg/core/state/notification_event.go +++ b/pkg/core/state/notification_event.go @@ -2,6 +2,7 @@ package state 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/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -21,7 +22,7 @@ type AppExecResult struct { Trigger trigger.Type VMState string GasConsumed util.Fixed8 - Stack string // JSON + Stack []smartcontract.Parameter Events []NotificationEvent } @@ -43,7 +44,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) { w.WriteB(byte(aer.Trigger)) w.WriteString(aer.VMState) aer.GasConsumed.EncodeBinary(w) - w.WriteString(aer.Stack) + w.WriteArray(aer.Stack) w.WriteArray(aer.Events) } @@ -53,6 +54,6 @@ func (aer *AppExecResult) DecodeBinary(r *io.BinReader) { aer.Trigger = trigger.Type(r.ReadB()) aer.VMState = r.ReadString() aer.GasConsumed.DecodeBinary(r) - aer.Stack = r.ReadString() + r.ReadArray(&aer.Stack) r.ReadArray(&aer.Events) } diff --git a/pkg/core/state/notification_event_test.go b/pkg/core/state/notification_event_test.go index 0f28e2957..3deb48845 100644 --- a/pkg/core/state/notification_event_test.go +++ b/pkg/core/state/notification_event_test.go @@ -5,6 +5,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/stretchr/testify/assert" ) @@ -30,7 +31,7 @@ func TestEncodeDecodeAppExecResult(t *testing.T) { Trigger: 1, VMState: "Hault", GasConsumed: 10, - Stack: "", + Stack: []smartcontract.Parameter{}, Events: []NotificationEvent{}, } buf := io.NewBufBinWriter() diff --git a/pkg/rpc/response/result/application_log.go b/pkg/rpc/response/result/application_log.go index 27e2026c0..da436f2f4 100644 --- a/pkg/rpc/response/result/application_log.go +++ b/pkg/rpc/response/result/application_log.go @@ -1,8 +1,6 @@ package result import ( - "encoding/json" - "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" @@ -18,12 +16,12 @@ type ApplicationLog struct { // Execution response wrapper type Execution struct { - Trigger string `json:"trigger"` - ScriptHash util.Uint160 `json:"contract"` - VMState string `json:"vmstate"` - GasConsumed util.Fixed8 `json:"gas_consumed"` - Stack json.RawMessage `json:"stack"` - Events []NotificationEvent `json:"notifications"` + Trigger string `json:"trigger"` + ScriptHash util.Uint160 `json:"contract"` + VMState string `json:"vmstate"` + GasConsumed util.Fixed8 `json:"gas_consumed"` + Stack []smartcontract.Parameter `json:"stack"` + Events []NotificationEvent `json:"notifications"` } //NotificationEvent response wrapper @@ -46,18 +44,12 @@ func NewApplicationLog(appExecRes *state.AppExecResult, scriptHash util.Uint160) triggerString := appExecRes.Trigger.String() - var rawStack json.RawMessage - if len(appExecRes.Stack) != 0 { - rawStack = json.RawMessage(appExecRes.Stack) - } else { - rawStack = json.RawMessage("[]") - } executions := []Execution{{ Trigger: triggerString, ScriptHash: scriptHash, VMState: appExecRes.VMState, GasConsumed: appExecRes.GasConsumed, - Stack: rawStack, + Stack: appExecRes.Stack, Events: events, }} diff --git a/pkg/smartcontract/parameter.go b/pkg/smartcontract/parameter.go index 0a5c8b9ff..a5d49669f 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -4,11 +4,14 @@ import ( "encoding/binary" "encoding/hex" "encoding/json" + "fmt" + "math" "math/bits" "strconv" "strings" "unicode/utf8" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/pkg/errors" ) @@ -210,6 +213,87 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) { return } +// EncodeBinary implements io.Serializable interface. +func (p *Parameter) EncodeBinary(w *io.BinWriter) { + w.WriteB(byte(p.Type)) + switch p.Type { + case BoolType: + w.WriteBool(p.Value.(bool)) + case ByteArrayType, PublicKeyType, SignatureType: + if p.Value == nil { + w.WriteVarUint(math.MaxUint64) + } else { + w.WriteVarBytes(p.Value.([]byte)) + } + case StringType: + w.WriteString(p.Value.(string)) + case IntegerType: + w.WriteU64LE(uint64(p.Value.(int64))) + case ArrayType: + w.WriteArray(p.Value.([]Parameter)) + case MapType: + m := p.Value.(map[Parameter]Parameter) + w.WriteVarUint(uint64(len(m))) + for k := range m { + v := m[k] + k.EncodeBinary(w) + v.EncodeBinary(w) + } + case Hash160Type: + w.WriteBytes(p.Value.(util.Uint160).BytesBE()) + case Hash256Type: + w.WriteBytes(p.Value.(util.Uint256).BytesBE()) + case InteropInterfaceType: + default: + w.Err = fmt.Errorf("unknown type: %x", p.Type) + } +} + +// DecodeBinary implements io.Serializable interface. +func (p *Parameter) DecodeBinary(r *io.BinReader) { + p.Type = ParamType(r.ReadB()) + switch p.Type { + case BoolType: + p.Value = r.ReadBool() + case ByteArrayType, PublicKeyType, SignatureType: + ln := r.ReadVarUint() + if ln != math.MaxUint64 { + b := make([]byte, ln) + r.ReadBytes(b) + p.Value = b + } + case StringType: + p.Value = r.ReadString() + case IntegerType: + p.Value = int64(r.ReadU64LE()) + case ArrayType: + ps := []Parameter{} + r.ReadArray(&ps) + p.Value = ps + case MapType: + ln := r.ReadVarUint() + m := make(map[Parameter]Parameter, ln) + for i := uint64(0); i < ln; i++ { + var k, v Parameter + k.DecodeBinary(r) + v.DecodeBinary(r) + m[k] = v + } + p.Value = m + case Hash160Type: + var u util.Uint160 + r.ReadBytes(u[:]) + p.Value = u + case Hash256Type: + var u util.Uint256 + r.ReadBytes(u[:]) + p.Value = u + case InteropInterfaceType: + default: + r.Err = fmt.Errorf("unknown type: %x", p.Type) + } +} + // Params is an array of Parameter (TODO: drop it?). type Params []Parameter diff --git a/pkg/smartcontract/parameter_test.go b/pkg/smartcontract/parameter_test.go index 55c8c2fd7..a1715ee13 100644 --- a/pkg/smartcontract/parameter_test.go +++ b/pkg/smartcontract/parameter_test.go @@ -6,8 +6,10 @@ import ( "reflect" "testing" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var marshalJSONTestCases = []struct { @@ -71,7 +73,7 @@ var marshalJSONTestCases = []struct { input: Parameter{ Type: MapType, Value: map[Parameter]Parameter{ - {Type: StringType, Value: "key1"}: {Type: IntegerType, Value: 1}, + {Type: StringType, Value: "key1"}: {Type: IntegerType, Value: int64(1)}, {Type: StringType, Value: "key2"}: {Type: StringType, Value: "two"}, }, }, @@ -441,3 +443,28 @@ func TestNewParameterFromString(t *testing.T) { } } } + +func TestEncodeDecodeBinary(t *testing.T) { + for _, tc := range marshalJSONTestCases { + w := io.NewBufBinWriter() + tc.input.EncodeBinary(w.BinWriter) + require.NoError(t, w.Err) + + r := io.NewBinReaderFromBuf(w.Bytes()) + var p Parameter + p.DecodeBinary(r) + require.NoError(t, r.Err) + require.Equal(t, tc.input, p) + } + + t.Run("unknown", func(t *testing.T) { + p := Parameter{Type: UnknownType} + w := io.NewBufBinWriter() + p.EncodeBinary(w.BinWriter) + require.Error(t, w.Err) + + r := io.NewBinReaderFromBuf([]byte{0xAA}) + p.DecodeBinary(r) + require.Error(t, r.Err) + }) +}