smartcontract: allow to use *big.Int numbers for integers

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2022-02-09 16:07:07 +03:00
parent 64186d0597
commit 0b0d39f797
7 changed files with 98 additions and 44 deletions

View file

@ -5,6 +5,7 @@ package client
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "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{ result, err := c.InvokeFunction(rmHash, "getDesignatedByRole", []smartcontract.Parameter{
{ {
Type: smartcontract.IntegerType, Type: smartcontract.IntegerType,
Value: int64(role), Value: big.NewInt(int64(role)),
}, },
{ {
Type: smartcontract.IntegerType, Type: smartcontract.IntegerType,
Value: int64(index), Value: big.NewInt(int64(index)),
}, },
}, nil) }, nil)
if err != nil { if err != nil {
@ -84,7 +85,7 @@ func (c *Client) NNSResolve(nnsHash util.Uint160, name string, typ nns.RecordTyp
}, },
{ {
Type: smartcontract.IntegerType, Type: smartcontract.IntegerType,
Value: int64(typ), Value: big.NewInt(int64(typ)),
}, },
}, nil) }, nil)
if err != nil { if err != nil {

View file

@ -15,7 +15,7 @@ func ParameterFromStackItem(i stackitem.Item, seen map[stackitem.Item]bool) Para
case *stackitem.BigInteger: case *stackitem.BigInteger:
return Parameter{ return Parameter{
Type: IntegerType, Type: IntegerType,
Value: i.Value().(*big.Int).Int64(), Value: i.Value().(*big.Int),
} }
case stackitem.Bool: case stackitem.Bool:
return Parameter{ return Parameter{

View file

@ -18,7 +18,7 @@ var toContractParameterTestCases = []struct {
stackitem.NewBool(true), stackitem.NewBool(true),
}), }),
result: Parameter{Type: ArrayType, Value: []Parameter{ result: Parameter{Type: ArrayType, Value: []Parameter{
{Type: IntegerType, Value: int64(1)}, {Type: IntegerType, Value: big.NewInt(1)},
{Type: BoolType, Value: true}, {Type: BoolType, Value: true},
}}, }},
}, },
@ -37,7 +37,7 @@ var toContractParameterTestCases = []struct {
{ {
input: stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewBool(true)}), input: stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewBool(true)}),
result: Parameter{Type: ArrayType, Value: []Parameter{ result: Parameter{Type: ArrayType, Value: []Parameter{
{Type: IntegerType, Value: int64(2)}, {Type: IntegerType, Value: big.NewInt(2)},
{Type: BoolType, Value: true}, {Type: BoolType, Value: true},
}}, }},
}, },
@ -55,11 +55,11 @@ var toContractParameterTestCases = []struct {
Type: MapType, Type: MapType,
Value: []ParameterPair{ Value: []ParameterPair{
{ {
Key: Parameter{Type: IntegerType, Value: int64(1)}, Key: Parameter{Type: IntegerType, Value: big.NewInt(1)},
Value: Parameter{Type: BoolType, Value: true}, Value: Parameter{Type: BoolType, Value: true},
}, { }, {
Key: Parameter{Type: ByteArrayType, Value: []byte("qwerty")}, 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}, Key: Parameter{Type: BoolType, Value: true},

View file

@ -5,13 +5,14 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"strconv" "math/big"
"strings" "strings"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "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. // 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") return nil, errors.New("invalid boolean value")
} }
case IntegerType: 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: case Hash160Type:
u, err := address.StringToUint160(val) u, err := address.StringToUint160(val)
if err == nil { if err == nil {
@ -250,8 +255,8 @@ func adjustValToType(typ ParamType, val string) (interface{}, error) {
func inferParamType(val string) ParamType { func inferParamType(val string) ParamType {
var err error var err error
_, err = strconv.Atoi(val) bi, ok := new(big.Int).SetString(val, 10)
if err == nil { if ok && stackitem.CheckIntegerSize(bi) == nil {
return IntegerType return IntegerType
} }

View file

@ -2,9 +2,11 @@ package smartcontract
import ( import (
"encoding/hex" "encoding/hex"
"math/big"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/util" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -76,6 +78,7 @@ func TestParseParamType(t *testing.T) {
} }
func TestInferParamType(t *testing.T) { func TestInferParamType(t *testing.T) {
bi := new(big.Int).Lsh(big.NewInt(1), stackitem.MaxBigIntegerSizeBits-2)
var inouts = []struct { var inouts = []struct {
in string in string
out ParamType out ParamType
@ -88,6 +91,15 @@ func TestInferParamType(t *testing.T) {
}, { }, {
in: "0", in: "0",
out: IntegerType, 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", in: "2e10",
out: ByteArrayType, out: ByteArrayType,
@ -150,6 +162,8 @@ func TestInferParamType(t *testing.T) {
} }
func TestAdjustValToType(t *testing.T) { func TestAdjustValToType(t *testing.T) {
bi := big.NewInt(1).Lsh(big.NewInt(1), stackitem.MaxBigIntegerSizeBits-2)
var inouts = []struct { var inouts = []struct {
typ ParamType typ ParamType
val string val string
@ -190,15 +204,23 @@ func TestAdjustValToType(t *testing.T) {
}, { }, {
typ: IntegerType, typ: IntegerType,
val: "0", val: "0",
out: int64(0), out: big.NewInt(0),
}, { }, {
typ: IntegerType, typ: IntegerType,
val: "42", val: "42",
out: int64(42), out: big.NewInt(42),
}, { }, {
typ: IntegerType, typ: IntegerType,
val: "-42", 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, typ: IntegerType,
val: "q", val: "q",

View file

@ -9,15 +9,17 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"math/big"
"math/bits" "math/bits"
"os" "os"
"strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
// Parameter represents a smart contract parameter. // Parameter represents a smart contract parameter.
@ -65,13 +67,12 @@ func (p Parameter) MarshalJSON() ([]byte, error) {
case BoolType, StringType, Hash160Type, Hash256Type: case BoolType, StringType, Hash160Type, Hash256Type:
resultRawValue, resultErr = json.Marshal(p.Value) resultRawValue, resultErr = json.Marshal(p.Value)
case IntegerType: case IntegerType:
val, ok := p.Value.(int64) val, ok := p.Value.(*big.Int)
if !ok { if !ok {
resultErr = errors.New("invalid integer value") resultErr = errors.New("invalid integer value")
break break
} }
valStr := strconv.FormatInt(val, 10) resultRawValue = json.RawMessage(`"` + val.String() + `"`)
resultRawValue = json.RawMessage(`"` + valStr + `"`)
case PublicKeyType, ByteArrayType, SignatureType: case PublicKeyType, ByteArrayType, SignatureType:
if p.Type == PublicKeyType { if p.Type == PublicKeyType {
resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte))) resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte)))
@ -145,17 +146,22 @@ func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
p.Value = s p.Value = s
case IntegerType: case IntegerType:
if err = json.Unmarshal(r.Value, &i); err == nil { if err = json.Unmarshal(r.Value, &i); err == nil {
p.Value = i p.Value = big.NewInt(i)
return return
} }
// sometimes integer comes as string // sometimes integer comes as string
if err = json.Unmarshal(r.Value, &s); err != nil { if jErr := json.Unmarshal(r.Value, &s); jErr != nil {
return return jErr
} }
if i, err = strconv.ParseInt(s, 10, 64); err != nil { bi, ok := new(big.Int).SetString(s, 10)
return 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: case ArrayType:
// https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67 // https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67
var rs []Parameter var rs []Parameter
@ -205,7 +211,8 @@ func (p *Parameter) EncodeBinary(w *io.BinWriter) {
case StringType: case StringType:
w.WriteString(p.Value.(string)) w.WriteString(p.Value.(string))
case IntegerType: case IntegerType:
w.WriteU64LE(uint64(p.Value.(int64))) val := p.Value.(*big.Int)
w.WriteVarBytes(bigint.ToBytes(val))
case ArrayType: case ArrayType:
w.WriteArray(p.Value.([]Parameter)) w.WriteArray(p.Value.([]Parameter))
case MapType: case MapType:
@ -236,7 +243,11 @@ func (p *Parameter) DecodeBinary(r *io.BinReader) {
case StringType: case StringType:
p.Value = r.ReadString() p.Value = r.ReadString()
case IntegerType: case IntegerType:
p.Value = int64(r.ReadU64LE()) bs := r.ReadVarBytes(bigint.MaxBytesLen)
if r.Err != nil {
return
}
p.Value = bigint.FromBytes(bs)
case ArrayType: case ArrayType:
ps := []Parameter{} ps := []Parameter{}
r.ReadArray(&ps) r.ReadArray(&ps)
@ -318,6 +329,9 @@ func (p Parameter) TryParse(dest interface{}) error {
return err return err
} }
return nil return nil
case **big.Int:
*dest = bigint.FromBytes(data)
return nil
case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint: case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint:
var size int var size int
switch dest.(type) { switch dest.(type) {

View file

@ -5,7 +5,9 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"math" "math"
"math/big"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
@ -22,9 +24,13 @@ var marshalJSONTestCases = []struct {
result string result string
}{ }{
{ {
input: Parameter{Type: IntegerType, Value: int64(12345)}, input: Parameter{Type: IntegerType, Value: big.NewInt(12345)},
result: `{"type":"Integer","value":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"}, input: Parameter{Type: StringType, Value: "Some string"},
result: `{"type":"String","value":"Some string"}`, result: `{"type":"String","value":"Some string"}`,
@ -57,7 +63,7 @@ var marshalJSONTestCases = []struct {
Type: ArrayType, Type: ArrayType,
Value: []Parameter{ Value: []Parameter{
{Type: StringType, Value: "str 1"}, {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}]}`, result: `{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}`,
@ -84,7 +90,7 @@ var marshalJSONTestCases = []struct {
Value: []ParameterPair{ Value: []ParameterPair{
{ {
Key: Parameter{Type: StringType, Value: "key1"}, 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"}, Key: Parameter{Type: StringType, Value: "key2"},
@ -102,7 +108,7 @@ var marshalJSONTestCases = []struct {
Key: Parameter{Type: StringType, Value: "key1"}, Key: Parameter{Type: StringType, Value: "key1"},
Value: Parameter{Type: ArrayType, Value: []Parameter{ Value: Parameter{Type: ArrayType, Value: []Parameter{
{Type: StringType, Value: "str 1"}, {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}`, 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"}`, 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") + `"}`, input: `{"type":"ByteString","value":"` + hexToBase64("010203") + `"}`,
@ -215,7 +221,7 @@ var unmarshalJSONTestCases = []struct {
Type: ArrayType, Type: ArrayType,
Value: []Parameter{ Value: []Parameter{
{Type: StringType, Value: "str 1"}, {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{ Value: []ParameterPair{
{ {
Key: Parameter{Type: StringType, Value: "key1"}, 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"}, Key: Parameter{Type: StringType, Value: "key2"},
@ -266,7 +272,7 @@ var unmarshalJSONTestCases = []struct {
Key: Parameter{Type: StringType, Value: "key1"}, Key: Parameter{Type: StringType, Value: "key1"},
Value: Parameter{Type: ArrayType, Value: []Parameter{ Value: Parameter{Type: ArrayType, Value: []Parameter{
{Type: StringType, Value: "str 1"}, {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": "String","value":1}`, // incorrect Value
`{"type": "Integer","value": "nn"}`, // incorrect Integer value `{"type": "Integer","value": "nn"}`, // incorrect Integer value
`{"type": "Integer","value": []}`, // 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": "Array","value": 123}`, // incorrect Array value
`{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value `{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value
`{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value `{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value
@ -330,7 +338,7 @@ func TestParam_UnmarshalJSON(t *testing.T) {
} }
for _, input := range unmarshalJSONErrorCases { 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}, input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
expected: int64(50686687331), expected: int64(50686687331),
}, },
{
input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
expected: big.NewInt(50686687331),
},
{ {
input: []byte("this is a test string"), input: []byte("this is a test string"),
expected: "this is a test string", expected: "this is a test string",
@ -450,13 +462,13 @@ func TestNewParameterFromString(t *testing.T) {
out: Parameter{StringType, "qwerty"}, out: Parameter{StringType, "qwerty"},
}, { }, {
in: "42", in: "42",
out: Parameter{IntegerType, int64(42)}, out: Parameter{IntegerType, big.NewInt(42)},
}, { }, {
in: "Hello, 世界", in: "Hello, 世界",
out: Parameter{StringType, "Hello, 世界"}, out: Parameter{StringType, "Hello, 世界"},
}, { }, {
in: `\4\2`, in: `\4\2`,
out: Parameter{IntegerType, int64(42)}, out: Parameter{IntegerType, big.NewInt(42)},
}, { }, {
in: `\\4\2`, in: `\\4\2`,
out: Parameter{StringType, `\42`}, out: Parameter{StringType, `\42`},
@ -465,7 +477,7 @@ func TestNewParameterFromString(t *testing.T) {
out: Parameter{StringType, `\42`}, out: Parameter{StringType, `\42`},
}, { }, {
in: "int:42", in: "int:42",
out: Parameter{IntegerType, int64(42)}, out: Parameter{IntegerType, big.NewInt(42)},
}, { }, {
in: "true", in: "true",
out: Parameter{BoolType, true}, out: Parameter{BoolType, true},
@ -544,8 +556,8 @@ func TestExpandParameterToEmitable(t *testing.T) {
Expected: true, Expected: true,
}, },
{ {
In: Parameter{Type: IntegerType, Value: int64(123)}, In: Parameter{Type: IntegerType, Value: big.NewInt(123)},
Expected: int64(123), Expected: big.NewInt(123),
}, },
{ {
In: Parameter{Type: ByteArrayType, Value: []byte{1, 2, 3}}, In: Parameter{Type: ByteArrayType, Value: []byte{1, 2, 3}},
@ -575,7 +587,7 @@ func TestExpandParameterToEmitable(t *testing.T) {
In: Parameter{Type: ArrayType, Value: []Parameter{ In: Parameter{Type: ArrayType, Value: []Parameter{
{ {
Type: IntegerType, Type: IntegerType,
Value: int64(123), Value: big.NewInt(123),
}, },
{ {
Type: ByteArrayType, 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() bw := io.NewBufBinWriter()