forked from TrueCloudLab/neoneo-go
smartcontract: store MapType Parameter as a slice of KV pairs
Fixes #809. Basically, there are three alternative approaches to fixing it: * allowing both []byte and string for ByteArrayType value minimal invasion into existing code, but ugly as hell and will probably backfire at some point * storing string values in ByteArrayType incurs quite a number of type conversions (and associated data copying), though note that these values are not changed usually, so dynamic properties of []byte are almost irrelevant here * storing only []byte values in ByteArrayType makes it impossible to use them as map keys which can be solved in several ways: - via an interface (Marshalable) which is good, but makes testing and comparing values in general harder, because of keys mismatch - using serialized Parameter as a key (in a string) which will need some additional marshaling/unmarshaling - converting MapType from map to a slice of key-value pairs not a bad idea as we don't use this map as a map really, the type itself is all about input/output for real VM types and this approach is also a bit closer to JSON representation of the Map
This commit is contained in:
parent
b502210ade
commit
3dbe549a61
4 changed files with 79 additions and 87 deletions
|
@ -35,6 +35,13 @@ type Parameter struct {
|
||||||
Value interface{} `json:"value"`
|
Value interface{} `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParameterPair represents key-value pair, a slice of which is stored in
|
||||||
|
// MapType Parameter.
|
||||||
|
type ParameterPair struct {
|
||||||
|
Key Parameter `json:"key"`
|
||||||
|
Value Parameter `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewParameter returns a Parameter with proper initialized Value
|
// NewParameter returns a Parameter with proper initialized Value
|
||||||
// of the given ParamType.
|
// of the given ParamType.
|
||||||
func NewParameter(t ParamType) Parameter {
|
func NewParameter(t ParamType) Parameter {
|
||||||
|
@ -49,16 +56,6 @@ type rawParameter struct {
|
||||||
Value json.RawMessage `json:"value"`
|
Value json.RawMessage `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyValuePair struct {
|
|
||||||
Key rawParameter `json:"key"`
|
|
||||||
Value rawParameter `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawKeyValuePair struct {
|
|
||||||
Key json.RawMessage `json:"key"`
|
|
||||||
Value json.RawMessage `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements Marshaler interface.
|
// MarshalJSON implements Marshaler interface.
|
||||||
func (p *Parameter) MarshalJSON() ([]byte, error) {
|
func (p *Parameter) MarshalJSON() ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
|
@ -95,28 +92,8 @@ func (p *Parameter) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
resultRawValue, resultErr = json.Marshal(value)
|
resultRawValue, resultErr = json.Marshal(value)
|
||||||
case MapType:
|
case MapType:
|
||||||
var value []keyValuePair
|
ppair := p.Value.([]ParameterPair)
|
||||||
for key, val := range p.Value.(map[Parameter]Parameter) {
|
resultRawValue, resultErr = json.Marshal(ppair)
|
||||||
rawKey, err := json.Marshal(key.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawValue, err := json.Marshal(val.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
value = append(value, keyValuePair{
|
|
||||||
Key: rawParameter{
|
|
||||||
Type: key.Type,
|
|
||||||
Value: rawKey,
|
|
||||||
},
|
|
||||||
Value: rawParameter{
|
|
||||||
Type: val.Type,
|
|
||||||
Value: rawValue,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
resultRawValue, resultErr = json.Marshal(value)
|
|
||||||
default:
|
default:
|
||||||
resultErr = errors.Errorf("Marshaller for type %s not implemented", p.Type)
|
resultErr = errors.Errorf("Marshaller for type %s not implemented", p.Type)
|
||||||
}
|
}
|
||||||
|
@ -181,22 +158,11 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
|
||||||
}
|
}
|
||||||
p.Value = rs
|
p.Value = rs
|
||||||
case MapType:
|
case MapType:
|
||||||
var rawMap []rawKeyValuePair
|
var ppair []ParameterPair
|
||||||
if err = json.Unmarshal(r.Value, &rawMap); err != nil {
|
if err = json.Unmarshal(r.Value, &ppair); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rs := make(map[Parameter]Parameter)
|
p.Value = ppair
|
||||||
for _, p := range rawMap {
|
|
||||||
var key, value Parameter
|
|
||||||
if err = json.Unmarshal(p.Key, &key); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal(p.Value, &value); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rs[key] = value
|
|
||||||
}
|
|
||||||
p.Value = rs
|
|
||||||
case Hash160Type:
|
case Hash160Type:
|
||||||
var h util.Uint160
|
var h util.Uint160
|
||||||
if err = json.Unmarshal(r.Value, &h); err != nil {
|
if err = json.Unmarshal(r.Value, &h); err != nil {
|
||||||
|
@ -234,13 +200,7 @@ func (p *Parameter) EncodeBinary(w *io.BinWriter) {
|
||||||
case ArrayType:
|
case ArrayType:
|
||||||
w.WriteArray(p.Value.([]Parameter))
|
w.WriteArray(p.Value.([]Parameter))
|
||||||
case MapType:
|
case MapType:
|
||||||
m := p.Value.(map[Parameter]Parameter)
|
w.WriteArray(p.Value.([]ParameterPair))
|
||||||
w.WriteVarUint(uint64(len(m)))
|
|
||||||
for k := range m {
|
|
||||||
v := m[k]
|
|
||||||
k.EncodeBinary(w)
|
|
||||||
v.EncodeBinary(w)
|
|
||||||
}
|
|
||||||
case Hash160Type:
|
case Hash160Type:
|
||||||
w.WriteBytes(p.Value.(util.Uint160).BytesBE())
|
w.WriteBytes(p.Value.(util.Uint160).BytesBE())
|
||||||
case Hash256Type:
|
case Hash256Type:
|
||||||
|
@ -273,15 +233,9 @@ func (p *Parameter) DecodeBinary(r *io.BinReader) {
|
||||||
r.ReadArray(&ps)
|
r.ReadArray(&ps)
|
||||||
p.Value = ps
|
p.Value = ps
|
||||||
case MapType:
|
case MapType:
|
||||||
ln := r.ReadVarUint()
|
ps := []ParameterPair{}
|
||||||
m := make(map[Parameter]Parameter, ln)
|
r.ReadArray(&ps)
|
||||||
for i := uint64(0); i < ln; i++ {
|
p.Value = ps
|
||||||
var k, v Parameter
|
|
||||||
k.DecodeBinary(r)
|
|
||||||
v.DecodeBinary(r)
|
|
||||||
m[k] = v
|
|
||||||
}
|
|
||||||
p.Value = m
|
|
||||||
case Hash160Type:
|
case Hash160Type:
|
||||||
var u util.Uint160
|
var u util.Uint160
|
||||||
r.ReadBytes(u[:])
|
r.ReadBytes(u[:])
|
||||||
|
@ -296,6 +250,18 @@ func (p *Parameter) DecodeBinary(r *io.BinReader) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements io.Serializable interface.
|
||||||
|
func (p *ParameterPair) EncodeBinary(w *io.BinWriter) {
|
||||||
|
p.Key.EncodeBinary(w)
|
||||||
|
p.Value.EncodeBinary(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
func (p *ParameterPair) DecodeBinary(r *io.BinReader) {
|
||||||
|
p.Key.DecodeBinary(r)
|
||||||
|
p.Value.DecodeBinary(r)
|
||||||
|
}
|
||||||
|
|
||||||
// Params is an array of Parameter (TODO: drop it?).
|
// Params is an array of Parameter (TODO: drop it?).
|
||||||
type Params []Parameter
|
type Params []Parameter
|
||||||
|
|
||||||
|
|
|
@ -72,9 +72,15 @@ var marshalJSONTestCases = []struct {
|
||||||
{
|
{
|
||||||
input: Parameter{
|
input: Parameter{
|
||||||
Type: MapType,
|
Type: MapType,
|
||||||
Value: map[Parameter]Parameter{
|
Value: []ParameterPair{
|
||||||
{Type: StringType, Value: "key1"}: {Type: IntegerType, Value: int64(1)},
|
{
|
||||||
{Type: StringType, Value: "key2"}: {Type: StringType, Value: "two"},
|
Key: Parameter{Type: StringType, Value: "key1"},
|
||||||
|
Value: Parameter{Type: IntegerType, Value: int64(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: Parameter{Type: StringType, Value: "key2"},
|
||||||
|
Value: Parameter{Type: StringType, Value: "two"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`,
|
result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`,
|
||||||
|
@ -82,11 +88,14 @@ var marshalJSONTestCases = []struct {
|
||||||
{
|
{
|
||||||
input: Parameter{
|
input: Parameter{
|
||||||
Type: MapType,
|
Type: MapType,
|
||||||
Value: map[Parameter]Parameter{
|
Value: []ParameterPair{
|
||||||
{Type: StringType, Value: "key1"}: {Type: ArrayType, Value: []Parameter{
|
{
|
||||||
{Type: StringType, Value: "str 1"},
|
Key: Parameter{Type: StringType, Value: "key1"},
|
||||||
{Type: IntegerType, Value: int64(2)},
|
Value: Parameter{Type: ArrayType, Value: []Parameter{
|
||||||
}},
|
{Type: StringType, Value: "str 1"},
|
||||||
|
{Type: IntegerType, Value: int64(2)},
|
||||||
|
}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`,
|
result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`,
|
||||||
|
@ -209,9 +218,15 @@ var unmarshalJSONTestCases = []struct {
|
||||||
input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`,
|
input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`,
|
||||||
result: Parameter{
|
result: Parameter{
|
||||||
Type: MapType,
|
Type: MapType,
|
||||||
Value: map[Parameter]Parameter{
|
Value: []ParameterPair{
|
||||||
{Type: StringType, Value: "key1"}: {Type: IntegerType, Value: int64(1)},
|
{
|
||||||
{Type: StringType, Value: "key2"}: {Type: StringType, Value: "two"},
|
Key: Parameter{Type: StringType, Value: "key1"},
|
||||||
|
Value: Parameter{Type: IntegerType, Value: int64(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: Parameter{Type: StringType, Value: "key2"},
|
||||||
|
Value: Parameter{Type: StringType, Value: "two"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -219,11 +234,14 @@ var unmarshalJSONTestCases = []struct {
|
||||||
input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`,
|
input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`,
|
||||||
result: Parameter{
|
result: Parameter{
|
||||||
Type: MapType,
|
Type: MapType,
|
||||||
Value: map[Parameter]Parameter{
|
Value: []ParameterPair{
|
||||||
{Type: StringType, Value: "key1"}: {Type: ArrayType, Value: []Parameter{
|
{
|
||||||
{Type: StringType, Value: "str 1"},
|
Key: Parameter{Type: StringType, Value: "key1"},
|
||||||
{Type: IntegerType, Value: int64(2)},
|
Value: Parameter{Type: ArrayType, Value: []Parameter{
|
||||||
}},
|
{Type: StringType, Value: "str 1"},
|
||||||
|
{Type: IntegerType, Value: int64(2)},
|
||||||
|
}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -480,16 +480,16 @@ func (i *MapItem) Dup() StackItem {
|
||||||
|
|
||||||
// ToContractParameter implements StackItem interface.
|
// ToContractParameter implements StackItem interface.
|
||||||
func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter {
|
func (i *MapItem) ToContractParameter(seen map[StackItem]bool) smartcontract.Parameter {
|
||||||
value := make(map[smartcontract.Parameter]smartcontract.Parameter)
|
value := make([]smartcontract.ParameterPair, 0)
|
||||||
if !seen[i] {
|
if !seen[i] {
|
||||||
seen[i] = true
|
seen[i] = true
|
||||||
for key, val := range i.value {
|
for key, val := range i.value {
|
||||||
pValue := val.ToContractParameter(seen)
|
pValue := val.ToContractParameter(seen)
|
||||||
pKey := fromMapKey(key).ToContractParameter(seen)
|
pKey := fromMapKey(key).ToContractParameter(seen)
|
||||||
if pKey.Type == smartcontract.ByteArrayType {
|
value = append(value, smartcontract.ParameterPair{
|
||||||
pKey.Value = string(pKey.Value.([]byte))
|
Key: pKey,
|
||||||
}
|
Value: pValue,
|
||||||
value[pKey] = pValue
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return smartcontract.Parameter{
|
return smartcontract.Parameter{
|
||||||
|
|
|
@ -421,10 +421,18 @@ var toContractParameterTestCases = []struct {
|
||||||
}},
|
}},
|
||||||
result: smartcontract.Parameter{
|
result: smartcontract.Parameter{
|
||||||
Type: smartcontract.MapType,
|
Type: smartcontract.MapType,
|
||||||
Value: map[smartcontract.Parameter]smartcontract.Parameter{
|
Value: []smartcontract.ParameterPair{
|
||||||
{Type: smartcontract.IntegerType, Value: int64(1)}: {Type: smartcontract.BoolType, Value: true},
|
{
|
||||||
{Type: smartcontract.ByteArrayType, Value: "qwerty"}: {Type: smartcontract.IntegerType, Value: int64(3)},
|
Key: smartcontract.Parameter{Type: smartcontract.IntegerType, Value: int64(1)},
|
||||||
{Type: smartcontract.BoolType, Value: true}: {Type: smartcontract.BoolType, Value: false},
|
Value: smartcontract.Parameter{Type: smartcontract.BoolType, Value: true},
|
||||||
|
}, {
|
||||||
|
Key: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte("qwerty")},
|
||||||
|
Value: smartcontract.Parameter{Type: smartcontract.IntegerType, Value: int64(3)},
|
||||||
|
}, {
|
||||||
|
|
||||||
|
Key: smartcontract.Parameter{Type: smartcontract.BoolType, Value: true},
|
||||||
|
Value: smartcontract.Parameter{Type: smartcontract.BoolType, Value: false},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue