diff --git a/pkg/rpc/client/native.go b/pkg/rpc/client/native.go index 7839ab941..85a857ac3 100644 --- a/pkg/rpc/client/native.go +++ b/pkg/rpc/client/native.go @@ -5,6 +5,7 @@ package client import ( "errors" "fmt" + "math/big" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" @@ -55,11 +56,11 @@ func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.Pu result, err := c.InvokeFunction(rmHash, "getDesignatedByRole", []smartcontract.Parameter{ { Type: smartcontract.IntegerType, - Value: int64(role), + Value: big.NewInt(int64(role)), }, { Type: smartcontract.IntegerType, - Value: int64(index), + Value: big.NewInt(int64(index)), }, }, nil) if err != nil { @@ -84,7 +85,7 @@ func (c *Client) NNSResolve(nnsHash util.Uint160, name string, typ nns.RecordTyp }, { Type: smartcontract.IntegerType, - Value: int64(typ), + Value: big.NewInt(int64(typ)), }, }, nil) if err != nil { diff --git a/pkg/smartcontract/convertor.go b/pkg/smartcontract/convertor.go deleted file mode 100644 index d4d9e45d7..000000000 --- a/pkg/smartcontract/convertor.go +++ /dev/null @@ -1,72 +0,0 @@ -package smartcontract - -import ( - "fmt" - "math/big" - - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" -) - -// ParameterFromStackItem converts stackitem.Item to Parameter. -func ParameterFromStackItem(i stackitem.Item, seen map[stackitem.Item]bool) Parameter { - switch t := i.(type) { - case stackitem.Null, *stackitem.Pointer: - return NewParameter(AnyType) - case *stackitem.BigInteger: - return Parameter{ - Type: IntegerType, - Value: i.Value().(*big.Int).Int64(), - } - case stackitem.Bool: - return Parameter{ - Type: BoolType, - Value: i.Value().(bool), - } - case *stackitem.ByteArray: - return Parameter{ - Type: ByteArrayType, - Value: i.Value().([]byte), - } - case *stackitem.Interop: - return Parameter{ - Type: InteropInterfaceType, - Value: nil, - } - case *stackitem.Buffer: - return Parameter{ - Type: ByteArrayType, - Value: i.Value().([]byte), - } - case *stackitem.Struct, *stackitem.Array: - var value []Parameter - - if !seen[i] { - seen[i] = true - for _, stackItem := range i.Value().([]stackitem.Item) { - parameter := ParameterFromStackItem(stackItem, seen) - value = append(value, parameter) - } - } - return Parameter{ - Type: ArrayType, - Value: value, - } - case *stackitem.Map: - value := make([]ParameterPair, 0) - if !seen[i] { - seen[i] = true - for _, element := range i.Value().([]stackitem.MapElement) { - value = append(value, ParameterPair{ - Key: ParameterFromStackItem(element.Key, seen), - Value: ParameterFromStackItem(element.Value, seen), - }) - } - } - return Parameter{ - Type: MapType, - Value: value, - } - default: - panic(fmt.Sprintf("unknown stack item type: %v", t)) - } -} diff --git a/pkg/smartcontract/convertor_test.go b/pkg/smartcontract/convertor_test.go deleted file mode 100644 index bd1585609..000000000 --- a/pkg/smartcontract/convertor_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package smartcontract - -import ( - "math/big" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/stretchr/testify/assert" -) - -var toContractParameterTestCases = []struct { - input stackitem.Item - result Parameter -}{ - { - input: stackitem.NewStruct([]stackitem.Item{ - stackitem.NewBigInteger(big.NewInt(1)), - stackitem.NewBool(true), - }), - result: Parameter{Type: ArrayType, Value: []Parameter{ - {Type: IntegerType, Value: int64(1)}, - {Type: BoolType, Value: true}, - }}, - }, - { - input: stackitem.NewBool(false), - result: Parameter{Type: BoolType, Value: false}, - }, - { - input: stackitem.NewByteArray([]byte{0x01, 0x02, 0x03}), - result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, - }, - { - input: stackitem.NewBuffer([]byte{0x01, 0x02, 0x03}), - result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}}, - }, - { - input: stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewBool(true)}), - result: Parameter{Type: ArrayType, Value: []Parameter{ - {Type: IntegerType, Value: int64(2)}, - {Type: BoolType, Value: true}, - }}, - }, - { - input: stackitem.NewInterop(nil), - result: Parameter{Type: InteropInterfaceType, Value: nil}, - }, - { - input: stackitem.NewMapWithValue([]stackitem.MapElement{ - {Key: stackitem.NewBigInteger(big.NewInt(1)), Value: stackitem.NewBool(true)}, - {Key: stackitem.NewByteArray([]byte("qwerty")), Value: stackitem.NewBigInteger(big.NewInt(3))}, - {Key: stackitem.NewBool(true), Value: stackitem.NewBool(false)}, - }), - result: Parameter{ - Type: MapType, - Value: []ParameterPair{ - { - Key: Parameter{Type: IntegerType, Value: int64(1)}, - Value: Parameter{Type: BoolType, Value: true}, - }, { - Key: Parameter{Type: ByteArrayType, Value: []byte("qwerty")}, - Value: Parameter{Type: IntegerType, Value: int64(3)}, - }, { - - Key: Parameter{Type: BoolType, Value: true}, - Value: Parameter{Type: BoolType, Value: false}, - }, - }, - }, - }, -} - -func TestToContractParameter(t *testing.T) { - for _, tc := range toContractParameterTestCases { - seen := make(map[stackitem.Item]bool) - res := ParameterFromStackItem(tc.input, seen) - assert.Equal(t, res, tc.result) - } -} diff --git a/pkg/smartcontract/param_type.go b/pkg/smartcontract/param_type.go index 2ea611a95..1f432536b 100644 --- a/pkg/smartcontract/param_type.go +++ b/pkg/smartcontract/param_type.go @@ -5,13 +5,14 @@ import ( "encoding/json" "errors" "fmt" - "strconv" + "math/big" "strings" "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/io" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // ParamType represents the Type of the smart contract parameter. @@ -208,7 +209,11 @@ func adjustValToType(typ ParamType, val string) (interface{}, error) { return nil, errors.New("invalid boolean value") } case IntegerType: - return strconv.ParseInt(val, 10, 64) + bi, ok := new(big.Int).SetString(val, 10) + if !ok || stackitem.CheckIntegerSize(bi) != nil { + return nil, errors.New("invalid integer value") + } + return bi, nil case Hash160Type: u, err := address.StringToUint160(val) if err == nil { @@ -250,8 +255,8 @@ func adjustValToType(typ ParamType, val string) (interface{}, error) { func inferParamType(val string) ParamType { var err error - _, err = strconv.Atoi(val) - if err == nil { + bi, ok := new(big.Int).SetString(val, 10) + if ok && stackitem.CheckIntegerSize(bi) == nil { return IntegerType } diff --git a/pkg/smartcontract/param_type_test.go b/pkg/smartcontract/param_type_test.go index 1d3c54819..0620e52e1 100644 --- a/pkg/smartcontract/param_type_test.go +++ b/pkg/smartcontract/param_type_test.go @@ -2,9 +2,11 @@ package smartcontract import ( "encoding/hex" + "math/big" "testing" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -76,6 +78,7 @@ func TestParseParamType(t *testing.T) { } func TestInferParamType(t *testing.T) { + bi := new(big.Int).Lsh(big.NewInt(1), stackitem.MaxBigIntegerSizeBits-2) var inouts = []struct { in string out ParamType @@ -88,6 +91,15 @@ func TestInferParamType(t *testing.T) { }, { in: "0", out: IntegerType, + }, { + in: "8765432187654321111", + out: IntegerType, + }, { + in: bi.String(), + out: IntegerType, + }, { + in: bi.String() + "0", // big for Integer but is still a valid hex + out: ByteArrayType, }, { in: "2e10", out: ByteArrayType, @@ -150,6 +162,8 @@ func TestInferParamType(t *testing.T) { } func TestAdjustValToType(t *testing.T) { + bi := big.NewInt(1).Lsh(big.NewInt(1), stackitem.MaxBigIntegerSizeBits-2) + var inouts = []struct { typ ParamType val string @@ -190,15 +204,23 @@ func TestAdjustValToType(t *testing.T) { }, { typ: IntegerType, val: "0", - out: int64(0), + out: big.NewInt(0), }, { typ: IntegerType, val: "42", - out: int64(42), + out: big.NewInt(42), }, { typ: IntegerType, val: "-42", - out: int64(-42), + out: big.NewInt(-42), + }, { + typ: IntegerType, + val: bi.String(), + out: bi, + }, { + typ: IntegerType, + val: bi.String() + "0", + err: true, }, { typ: IntegerType, val: "q", diff --git a/pkg/smartcontract/parameter.go b/pkg/smartcontract/parameter.go index 91f773fed..55683fa1a 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -8,16 +8,16 @@ import ( "encoding/json" "errors" "fmt" - "math" + "math/big" "math/bits" "os" - "strconv" "strings" "unicode/utf8" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Parameter represents a smart contract parameter. @@ -65,13 +65,12 @@ func (p Parameter) MarshalJSON() ([]byte, error) { case BoolType, StringType, Hash160Type, Hash256Type: resultRawValue, resultErr = json.Marshal(p.Value) case IntegerType: - val, ok := p.Value.(int64) + val, ok := p.Value.(*big.Int) if !ok { resultErr = errors.New("invalid integer value") break } - valStr := strconv.FormatInt(val, 10) - resultRawValue = json.RawMessage(`"` + valStr + `"`) + resultRawValue = json.RawMessage(`"` + val.String() + `"`) case PublicKeyType, ByteArrayType, SignatureType: if p.Type == PublicKeyType { resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte))) @@ -145,17 +144,22 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) { p.Value = s case IntegerType: if err = json.Unmarshal(r.Value, &i); err == nil { - p.Value = i + p.Value = big.NewInt(i) return } // sometimes integer comes as string - if err = json.Unmarshal(r.Value, &s); err != nil { - return + if jErr := json.Unmarshal(r.Value, &s); jErr != nil { + return jErr } - if i, err = strconv.ParseInt(s, 10, 64); err != nil { - return + bi, ok := new(big.Int).SetString(s, 10) + if !ok { + // In this case previous err should mean string contains non-digit characters. + return err + } + err = stackitem.CheckIntegerSize(bi) + if err == nil { + p.Value = bi } - p.Value = i case ArrayType: // https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67 var rs []Parameter @@ -190,87 +194,6 @@ 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: - w.WriteArray(p.Value.([]ParameterPair)) - case Hash160Type: - w.WriteBytes(p.Value.(util.Uint160).BytesBE()) - case Hash256Type: - w.WriteBytes(p.Value.(util.Uint256).BytesBE()) - case InteropInterfaceType, AnyType: - 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: - ps := []ParameterPair{} - r.ReadArray(&ps) - p.Value = ps - 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, AnyType: - default: - r.Err = fmt.Errorf("unknown type: %x", p.Type) - } -} - -// 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?). type Params []Parameter @@ -318,6 +241,9 @@ func (p Parameter) TryParse(dest interface{}) error { return err } return nil + case **big.Int: + *dest = bigint.FromBytes(data) + return nil case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint: var size int switch dest.(type) { diff --git a/pkg/smartcontract/parameter_test.go b/pkg/smartcontract/parameter_test.go index 82aba2317..e52cc67a1 100644 --- a/pkg/smartcontract/parameter_test.go +++ b/pkg/smartcontract/parameter_test.go @@ -5,10 +5,11 @@ import ( "encoding/hex" "encoding/json" "math" + "math/big" "reflect" + "strings" "testing" - "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" @@ -22,9 +23,13 @@ var marshalJSONTestCases = []struct { result string }{ { - input: Parameter{Type: IntegerType, Value: int64(12345)}, + input: Parameter{Type: IntegerType, Value: big.NewInt(12345)}, result: `{"type":"Integer","value":12345}`, }, + { + input: Parameter{Type: IntegerType, Value: new(big.Int).Lsh(big.NewInt(1), 254)}, + result: `{"type":"Integer","value":"` + new(big.Int).Lsh(big.NewInt(1), 254).String() + `"}`, + }, { input: Parameter{Type: StringType, Value: "Some string"}, result: `{"type":"String","value":"Some string"}`, @@ -57,7 +62,7 @@ var marshalJSONTestCases = []struct { Type: ArrayType, Value: []Parameter{ {Type: StringType, Value: "str 1"}, - {Type: IntegerType, Value: int64(2)}, + {Type: IntegerType, Value: big.NewInt(2)}, }, }, result: `{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}`, @@ -84,7 +89,7 @@ var marshalJSONTestCases = []struct { Value: []ParameterPair{ { Key: Parameter{Type: StringType, Value: "key1"}, - Value: Parameter{Type: IntegerType, Value: int64(1)}, + Value: Parameter{Type: IntegerType, Value: big.NewInt(1)}, }, { Key: Parameter{Type: StringType, Value: "key2"}, @@ -102,7 +107,7 @@ var marshalJSONTestCases = []struct { Key: Parameter{Type: StringType, Value: "key1"}, Value: Parameter{Type: ArrayType, Value: []Parameter{ {Type: StringType, Value: "str 1"}, - {Type: IntegerType, Value: int64(2)}, + {Type: IntegerType, Value: big.NewInt(2)}, }}, }, }, @@ -185,11 +190,11 @@ var unmarshalJSONTestCases = []struct { }, { input: `{"type":"Integer","value":12345}`, - result: Parameter{Type: IntegerType, Value: int64(12345)}, + result: Parameter{Type: IntegerType, Value: big.NewInt(12345)}, }, { input: `{"type":"Integer","value":"12345"}`, - result: Parameter{Type: IntegerType, Value: int64(12345)}, + result: Parameter{Type: IntegerType, Value: big.NewInt(12345)}, }, { input: `{"type":"ByteString","value":"` + hexToBase64("010203") + `"}`, @@ -215,7 +220,7 @@ var unmarshalJSONTestCases = []struct { Type: ArrayType, Value: []Parameter{ {Type: StringType, Value: "str 1"}, - {Type: IntegerType, Value: int64(2)}, + {Type: IntegerType, Value: big.NewInt(2)}, }, }, }, @@ -248,7 +253,7 @@ var unmarshalJSONTestCases = []struct { Value: []ParameterPair{ { Key: Parameter{Type: StringType, Value: "key1"}, - Value: Parameter{Type: IntegerType, Value: int64(1)}, + Value: Parameter{Type: IntegerType, Value: big.NewInt(1)}, }, { Key: Parameter{Type: StringType, Value: "key2"}, @@ -266,7 +271,7 @@ var unmarshalJSONTestCases = []struct { Key: Parameter{Type: StringType, Value: "key1"}, Value: Parameter{Type: ArrayType, Value: []Parameter{ {Type: StringType, Value: "str 1"}, - {Type: IntegerType, Value: int64(2)}, + {Type: IntegerType, Value: big.NewInt(2)}, }}, }, }, @@ -310,6 +315,8 @@ var unmarshalJSONErrorCases = []string{ `{"type": "String","value":1}`, // incorrect Value `{"type": "Integer","value": "nn"}`, // incorrect Integer value `{"type": "Integer","value": []}`, // incorrect Integer value + `{"type": "Integer","value":"` + + strings.Repeat("9", 100) + `"}`, // too big Integer `{"type": "Array","value": 123}`, // incorrect Array value `{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value `{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value @@ -330,7 +337,7 @@ func TestParam_UnmarshalJSON(t *testing.T) { } for _, input := range unmarshalJSONErrorCases { - assert.Error(t, json.Unmarshal([]byte(input), &s)) + assert.Error(t, json.Unmarshal([]byte(input), &s), input) } } @@ -370,6 +377,10 @@ var tryParseTestCases = []struct { input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b}, expected: int64(50686687331), }, + { + input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b}, + expected: big.NewInt(50686687331), + }, { input: []byte("this is a test string"), expected: "this is a test string", @@ -450,13 +461,13 @@ func TestNewParameterFromString(t *testing.T) { out: Parameter{StringType, "qwerty"}, }, { in: "42", - out: Parameter{IntegerType, int64(42)}, + out: Parameter{IntegerType, big.NewInt(42)}, }, { in: "Hello, 世界", out: Parameter{StringType, "Hello, 世界"}, }, { in: `\4\2`, - out: Parameter{IntegerType, int64(42)}, + out: Parameter{IntegerType, big.NewInt(42)}, }, { in: `\\4\2`, out: Parameter{StringType, `\42`}, @@ -465,7 +476,7 @@ func TestNewParameterFromString(t *testing.T) { out: Parameter{StringType, `\42`}, }, { in: "int:42", - out: Parameter{IntegerType, int64(42)}, + out: Parameter{IntegerType, big.NewInt(42)}, }, { in: "true", out: Parameter{BoolType, true}, @@ -514,20 +525,6 @@ func TestNewParameterFromString(t *testing.T) { } } -func TestEncodeDecodeBinary(t *testing.T) { - for _, tc := range marshalJSONTestCases { - testserdes.EncodeDecodeBinary(t, &tc.input, new(Parameter)) - } - - t.Run("unknown", func(t *testing.T) { - p := Parameter{Type: UnknownType} - _, err := testserdes.EncodeBinary(&p) - require.Error(t, err) - - require.Error(t, testserdes.DecodeBinary([]byte{0xAA}, &p)) - }) -} - func hexToBase64(s string) string { b, _ := hex.DecodeString(s) return base64.StdEncoding.EncodeToString(b) @@ -544,8 +541,8 @@ func TestExpandParameterToEmitable(t *testing.T) { Expected: true, }, { - In: Parameter{Type: IntegerType, Value: int64(123)}, - Expected: int64(123), + In: Parameter{Type: IntegerType, Value: big.NewInt(123)}, + Expected: big.NewInt(123), }, { In: Parameter{Type: ByteArrayType, Value: []byte{1, 2, 3}}, @@ -575,7 +572,7 @@ func TestExpandParameterToEmitable(t *testing.T) { In: Parameter{Type: ArrayType, Value: []Parameter{ { Type: IntegerType, - Value: int64(123), + Value: big.NewInt(123), }, { Type: ByteArrayType, @@ -591,7 +588,7 @@ func TestExpandParameterToEmitable(t *testing.T) { }, }, }}, - Expected: []interface{}{int64(123), []byte{1, 2, 3}, []interface{}{true}}, + Expected: []interface{}{big.NewInt(123), []byte{1, 2, 3}, []interface{}{true}}, }, } bw := io.NewBufBinWriter()