From 0b0d39f797e6077829d9de52fe31c1c446b93229 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Wed, 9 Feb 2022 16:07:07 +0300 Subject: [PATCH] smartcontract: allow to use `*big.Int` numbers for integers Signed-off-by: Evgeniy Stratonikov --- pkg/rpc/client/native.go | 7 +++-- pkg/smartcontract/convertor.go | 2 +- pkg/smartcontract/convertor_test.go | 8 ++--- pkg/smartcontract/param_type.go | 13 +++++--- pkg/smartcontract/param_type_test.go | 28 +++++++++++++++-- pkg/smartcontract/parameter.go | 38 +++++++++++++++-------- pkg/smartcontract/parameter_test.go | 46 ++++++++++++++++++---------- 7 files changed, 98 insertions(+), 44 deletions(-) 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 index d4d9e45d7..fe977a91d 100644 --- a/pkg/smartcontract/convertor.go +++ b/pkg/smartcontract/convertor.go @@ -15,7 +15,7 @@ func ParameterFromStackItem(i stackitem.Item, seen map[stackitem.Item]bool) Para case *stackitem.BigInteger: return Parameter{ Type: IntegerType, - Value: i.Value().(*big.Int).Int64(), + Value: i.Value().(*big.Int), } case stackitem.Bool: return Parameter{ diff --git a/pkg/smartcontract/convertor_test.go b/pkg/smartcontract/convertor_test.go index bd1585609..3aaae34d5 100644 --- a/pkg/smartcontract/convertor_test.go +++ b/pkg/smartcontract/convertor_test.go @@ -18,7 +18,7 @@ var toContractParameterTestCases = []struct { stackitem.NewBool(true), }), result: Parameter{Type: ArrayType, Value: []Parameter{ - {Type: IntegerType, Value: int64(1)}, + {Type: IntegerType, Value: big.NewInt(1)}, {Type: BoolType, Value: true}, }}, }, @@ -37,7 +37,7 @@ var toContractParameterTestCases = []struct { { 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: IntegerType, Value: big.NewInt(2)}, {Type: BoolType, Value: true}, }}, }, @@ -55,11 +55,11 @@ var toContractParameterTestCases = []struct { Type: MapType, Value: []ParameterPair{ { - Key: Parameter{Type: IntegerType, Value: int64(1)}, + Key: Parameter{Type: IntegerType, Value: big.NewInt(1)}, Value: Parameter{Type: BoolType, Value: true}, }, { Key: Parameter{Type: ByteArrayType, Value: []byte("qwerty")}, - Value: Parameter{Type: IntegerType, Value: int64(3)}, + Value: Parameter{Type: IntegerType, Value: big.NewInt(3)}, }, { Key: Parameter{Type: BoolType, Value: true}, 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..02f84e32c 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -9,15 +9,17 @@ import ( "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/encoding/bigint" "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" ) // Parameter represents a smart contract parameter. @@ -65,13 +67,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 +146,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 @@ -205,7 +211,8 @@ func (p *Parameter) EncodeBinary(w *io.BinWriter) { case StringType: w.WriteString(p.Value.(string)) case IntegerType: - w.WriteU64LE(uint64(p.Value.(int64))) + val := p.Value.(*big.Int) + w.WriteVarBytes(bigint.ToBytes(val)) case ArrayType: w.WriteArray(p.Value.([]Parameter)) case MapType: @@ -236,7 +243,11 @@ func (p *Parameter) DecodeBinary(r *io.BinReader) { case StringType: p.Value = r.ReadString() case IntegerType: - p.Value = int64(r.ReadU64LE()) + bs := r.ReadVarBytes(bigint.MaxBytesLen) + if r.Err != nil { + return + } + p.Value = bigint.FromBytes(bs) case ArrayType: ps := []Parameter{} r.ReadArray(&ps) @@ -318,6 +329,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..8fd87410f 100644 --- a/pkg/smartcontract/parameter_test.go +++ b/pkg/smartcontract/parameter_test.go @@ -5,7 +5,9 @@ import ( "encoding/hex" "encoding/json" "math" + "math/big" "reflect" + "strings" "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" @@ -22,9 +24,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 +63,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 +90,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 +108,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 +191,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 +221,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 +254,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 +272,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 +316,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 +338,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 +378,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 +462,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 +477,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}, @@ -544,8 +556,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 +587,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 +603,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()