Skip to content

Commit

Permalink
Implemented support for int128, uint128 and float128 in ABI serializer.
Browse files Browse the repository at this point in the history
This first implementation does not provide a numerical view of those numbers,
but we can easily implement a big.Int converter on top.

Users might find convenient to have two `uint64` when dealing with those
types.
  • Loading branch information
abourget committed Nov 9, 2018
1 parent 4f022e5 commit 91879e8
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 17 deletions.
8 changes: 5 additions & 3 deletions abidecoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,15 @@ func (a *ABI) read(binaryDecoder *Decoder, fieldName string, fieldType string, j
case "uint32":
value, err = binaryDecoder.ReadUint32()
case "int64":
// This should be a JSONInt64
value, err = binaryDecoder.ReadInt64()
case "uint64":
// This should be a JSONUint64
value, err = binaryDecoder.ReadUint64()
case "int128":
err = fmt.Errorf("int128 support not implemented")
value, err = binaryDecoder.ReadUint128("int128")
case "uint128":
err = fmt.Errorf("uint128 support not implemented")
value, err = binaryDecoder.ReadUint128("uint128")
case "varint32":
value, err = binaryDecoder.ReadVarint32()
case "varuint32":
Expand All @@ -179,7 +181,7 @@ func (a *ABI) read(binaryDecoder *Decoder, fieldName string, fieldType string, j
case "float64":
value, err = binaryDecoder.ReadFloat64()
case "float128":
err = fmt.Errorf("float128 support not implemented")
value, err = binaryDecoder.ReadUint128("float128")
case "bool":
value, err = binaryDecoder.ReadBool()
case "time_point":
Expand Down
6 changes: 3 additions & 3 deletions abidecoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ func TestABI_Read(t *testing.T) {
{"caseName": "max int64", "typeName": "int64", "value": "9223372036854775807", "encode": int64(9223372036854775807), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "min uint64", "typeName": "uint64", "value": "0", "encode": uint64(0), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "max uint64", "typeName": "uint64", "value": "18446744073709551615", "encode": uint64(18446744073709551615), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "int128 unsupported", "typeName": "int128", "value": "", "encode": int64(1), "expectedError": fmt.Errorf("decoding field [testedField] of type [int128]: read: int128 support not implemented"), "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "uint128 unsupported", "typeName": "uint128", "value": "", "encode": uint64(1), "expectedError": fmt.Errorf("decoding field [testedField] of type [uint128]: read: uint128 support not implemented"), "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "int128", "typeName": "int128", "value": `"0x01000000000000000200000000000000"`, "encode": Int128{Lo: 1, Hi: 2}, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "uint128", "typeName": "uint128", "value": `"0x01000000000000000200000000000000"`, "encode": Uint128{Lo: 1, Hi: 2}, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "min varint32", "typeName": "varint32", "value": "-2147483648", "encode": Varint32(-2147483648), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "max varint32", "typeName": "varint32", "value": "2147483647", "encode": Varint32(2147483647), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "min varuint32", "typeName": "varuint32", "value": "0", "encode": Varuint32(0), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
Expand All @@ -387,7 +387,7 @@ func TestABI_Read(t *testing.T) {
{"caseName": "max float 32", "typeName": "float32", "value": "340282346638528860000000000000000000000", "encode": float32(math.MaxFloat32), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "min float64", "typeName": "float64", "value": "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", "encode": math.SmallestNonzeroFloat64, "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "max float64", "typeName": "float64", "value": "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "encode": math.MaxFloat64, "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "float128 unsupported", "typeName": "float128", "value": uint64(1), "encode": uint64(1), "expectedError": fmt.Errorf("decoding field [testedField] of type [float128]: read: float128 support not implemented"), "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "float128", "typeName": "float128", "value": `"0x01000000000000000200000000000000"`, "encode": Float128{Lo: 1, Hi: 2}, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "bool true", "typeName": "bool", "value": "true", "encode": true, "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "bool false", "typeName": "bool", "value": "false", "encode": false, "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "time_point", "typeName": "time_point", "value": "\"2018-11-01T15:13:07.001\"", "encode": TimePoint(1541085187001001), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
Expand Down
19 changes: 15 additions & 4 deletions abiencoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,17 @@ func (a *ABI) writeField(binaryEncoder *Encoder, fieldName string, fieldType str
}
object = i
case "int128":
return fmt.Errorf("writing field: int128 support not implemented")
var in Int128
if err := json.Unmarshal([]byte(value.Raw), &in); err != nil {
return err
}
object = in
case "uint128":
return fmt.Errorf("writing field: uint128 support not implemented")
var in Uint128
if err := json.Unmarshal([]byte(value.Raw), &in); err != nil {
return err
}
object = in
case "float32":
f, err := valueToFloat(fieldName, value, 32)
if err != nil {
Expand All @@ -204,7 +212,11 @@ func (a *ABI) writeField(binaryEncoder *Encoder, fieldName string, fieldType str
}
object = f
case "float128":
return fmt.Errorf("writing field: float128 support not implemented")
var in Float128
if err := json.Unmarshal([]byte(value.Raw), &in); err != nil {
return err
}
object = in
case "bool":
object = value.Bool()
case "time_point_sec":
Expand Down Expand Up @@ -316,7 +328,6 @@ func (a *ABI) writeField(binaryEncoder *Encoder, fieldName string, fieldType str

Logger.ABIEncoder.Printf("Writing object [%s]\n", object)
return binaryEncoder.Encode(object)

}

func valueToInt(fieldName string, value gjson.Result, bitSize int) (int64, error) {
Expand Down
6 changes: 3 additions & 3 deletions abiencoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ func TestABI_Write(t *testing.T) {
{"caseName": "max uint64", "typeName": "uint64", "expectedValue": "ffffffffffffffff", "json": "{\"testField\":18446744073709551615}", "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "out of range uint64", "typeName": "uint64", "expectedValue": "", "json": "{\"testField\":-1}", "expectedError": fmt.Errorf("writing field: [test_field_name] type uint64 : strconv.ParseUint: parsing \"-1\": invalid syntax")},
{"caseName": "out of range uint64", "typeName": "uint64", "expectedValue": "", "json": "{\"testField\":18446744073709551616}", "expectedError": fmt.Errorf("writing field: [test_field_name] type uint64 : strconv.ParseUint: parsing \"18446744073709551616\": value out of range")},
{"caseName": "int128 unsupported", "typeName": "int128", "expectedValue": "", "json": "{\"testField\":18446744073709551616}", "expectedError": fmt.Errorf("writing field: int128 support not implemented")},
{"caseName": "uint128 unsupported", "typeName": "uint128", "expectedValue": "", "json": "{\"testField\":18446744073709551616}", "expectedError": fmt.Errorf("writing field: uint128 support not implemented")},
{"caseName": "int128", "typeName": "int128", "expectedValue": "01020000000000000200000000000000", "json": "{\"testField\":\"0x01020000000000000200000000000000\"}"},
{"caseName": "uint128", "typeName": "uint128", "expectedValue": "01000000000000000200000000000000", "json": "{\"testField\":\"0x01000000000000000200000000000000\"}"},
{"caseName": "varint32", "typeName": "varint32", "expectedValue": "00000080", "json": "{\"testField\":-2147483648}", "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "varuint32", "typeName": "varuint32", "expectedValue": "ffffffff", "json": "{\"testField\":4294967295}", "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"}, //{"caseName": "min varuint32", "typeName": "varuint32", "expectedValue": "0", "json": Varuint32(0), "expectedError": nil, "isOptional": false, "isArray": false, "fieldName": "testedField"},
{"caseName": "min float32", "typeName": "float32", "expectedValue": "01000000", "json": "{\"testField\":0.000000000000000000000000000000000000000000001401298464324817}", "expectedError": nil},
Expand All @@ -253,7 +253,7 @@ func TestABI_Write(t *testing.T) {
{"caseName": "min float64", "typeName": "float64", "expectedValue": "0100000000000000", "json": "{\"testField\":0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005}", "expectedError": nil},
{"caseName": "max float64", "typeName": "float64", "expectedValue": "ffffffffffffef7f", "json": "{\"testField\":179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000}", "expectedError": nil},
{"caseName": "err float64", "typeName": "float64", "expectedValue": "ffffffffffffef7f", "json": "{\"testField\":279769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000}", "expectedError": fmt.Errorf("writing field: [test_field_name] type float64 : strconv.ParseFloat: parsing \"279769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\": value out of range")},
{"caseName": "float128 unsupported", "typeName": "float128", "expectedValue": "", "json": "{\"testField\":0}", "expectedError": fmt.Errorf("writing field: float128 support not implemented")},
{"caseName": "float128", "typeName": "float128", "expectedValue": "ffffffffffffef7fffffffffffffef7f", "json": "{\"testField\":\"0xffffffffffffef7fffffffffffffef7f\"}"},
{"caseName": "bool true", "typeName": "bool", "expectedValue": "01", "json": "{\"testField\":true}", "expectedError": nil},
{"caseName": "bool false", "typeName": "bool", "expectedValue": "00", "json": "{\"testField\":false}", "expectedError": nil},
{"caseName": "time_point", "typeName": "time_point", "expectedValue": "0100000000000000", "json": "{\"testField\":\"1970-01-01T00:00:00.001\"", "expectedError": nil},
Expand Down
32 changes: 32 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var TypeSize = struct {
Int16 int
UInt32 int
UInt64 int
UInt128 int
Float32 int
Float64 int
SHA256Bytes int
Expand All @@ -48,6 +49,7 @@ var TypeSize = struct {
Int16: 2,
UInt32: 4,
UInt64: 8,
UInt128: 16,
Float32: 4,
Float64: 8,
SHA256Bytes: 32,
Expand Down Expand Up @@ -159,6 +161,21 @@ func (d *Decoder) Decode(v interface{}) (err error) {
n, err = d.ReadFloat64()
rv.SetFloat(n)
return
case *Uint128:
var n Uint128
n, err = d.ReadUint128("uint128")
rv.Set(reflect.ValueOf(n))
return
case *Int128:
var n Uint128
n, err = d.ReadUint128("int128")
rv.Set(reflect.ValueOf(Int128(n)))
return
case *Float128:
var n Uint128
n, err = d.ReadUint128("float128")
rv.Set(reflect.ValueOf(Float128(n)))
return
case *uint16:
var n uint16
n, err = d.ReadUint16()
Expand Down Expand Up @@ -567,6 +584,21 @@ func (d *Decoder) ReadUint64() (out uint64, err error) {
return
}

func (d *Decoder) ReadUint128(typeName string) (out Uint128, err error) {
if d.remaining() < TypeSize.UInt128 {
err = fmt.Errorf("%s required [%d] bytes, remaining [%d]", typeName, TypeSize.UInt128, d.remaining())
return
}

data := d.data[d.pos : d.pos+TypeSize.UInt128]
out.Lo = binary.LittleEndian.Uint64(data)
out.Hi = binary.LittleEndian.Uint64(data[8:])

d.pos += TypeSize.UInt128
Logger.Decoder.Print(fmt.Sprintf("readUint128 [%d] [%s]", out, hex.EncodeToString(data)))
return
}

func (d *Decoder) ReadFloat32() (out float32, err error) {
if d.remaining() < TypeSize.Float32 {
err = fmt.Errorf("float32 required [%d] bytes, remaining [%d]", TypeSize.Float32, d.remaining())
Expand Down
1 change: 0 additions & 1 deletion decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ func TestDecoder_BlockTimestamp(t *testing.T) {
}

func TestDecoder_Time(t *testing.T) {

time := time.Now()

buf := new(bytes.Buffer)
Expand Down
17 changes: 14 additions & 3 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ func (e *Encoder) Encode(v interface{}) (err error) {
return e.writeFloat64(cv)
case Varint32:
return e.writeVarInt(int(cv))
case Uint128:
return e.writeUint128(cv)
case Int128:
fmt.Println("CAMILON")
return e.writeUint128(Uint128(cv))
case Float128:
return e.writeUint128(Uint128(cv))
case Varuint32:
return e.writeUVarInt(int(cv))
case bool:
Expand All @@ -106,8 +113,6 @@ func (e *Encoder) Encode(v interface{}) (err error) {
return e.writeChecksum512(cv)
case []byte:
return e.writeByteArray(cv)
case SHA256Bytes:
return e.writeSHA256Bytes(cv)
case ecc.PublicKey:
return e.writePublicKey(cv)
case ecc.Signature:
Expand Down Expand Up @@ -295,7 +300,6 @@ func (e *Encoder) writeUint32(i uint32) (err error) {
buf := make([]byte, TypeSize.UInt32)
binary.LittleEndian.PutUint32(buf, i)
return e.toWriter(buf)

}

func (e *Encoder) writeInt64(i int64) (err error) {
Expand All @@ -308,7 +312,14 @@ func (e *Encoder) writeUint64(i uint64) (err error) {
buf := make([]byte, TypeSize.UInt64)
binary.LittleEndian.PutUint64(buf, i)
return e.toWriter(buf)
}

func (e *Encoder) writeUint128(i Uint128) (err error) {
Logger.Encoder.Printf("Writing uint128 [%d]\n", i)
buf := make([]byte, TypeSize.UInt128)
binary.LittleEndian.PutUint64(buf, i.Lo)
binary.LittleEndian.PutUint64(buf[TypeSize.UInt64:], i.Hi)
return e.toWriter(buf)
}

func (e *Encoder) writeFloat32(f float32) (err error) {
Expand Down
114 changes: 114 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,3 +628,117 @@ func (i *JSONInt64) UnmarshalJSON(data []byte) error {

return nil
}

type Uint128 struct {
Lo uint64
Hi uint64
}

type Int128 Uint128

type Float128 Uint128

// func (i Int128) BigInt() *big.Int {
// // decode the Lo and Hi to handle the sign
// return nil
// }

// func (i Uint128) BigInt() *big.Int {
// // no sign to handle, all good..
// return nil
// }

// func NewInt128(i *big.Int) (Int128, error) {
// // if the big Int overflows the JSONInt128 limits..
// return Int128{}, nil
// }

// func NewUint128(i *big.Int) (Uint128, error) {
// // if the big Int overflows the JSONInt128 limits..
// return Uint128{}, nil
// }

func (i Uint128) MarshalJSON() (data []byte, err error) {
return json.Marshal(i.String())
}

func (i Int128) MarshalJSON() (data []byte, err error) {
return json.Marshal(Uint128(i).String())
}

func (i Float128) MarshalJSON() (data []byte, err error) {
return json.Marshal(Uint128(i).String())
}

func (i Uint128) String() string {
// Same for Int128, Float128
number := make([]byte, 16)
binary.LittleEndian.PutUint64(number[:], i.Lo)
binary.LittleEndian.PutUint64(number[8:], i.Hi)
return fmt.Sprintf("0x%s%s", hex.EncodeToString(number[:8]), hex.EncodeToString(number[8:]))
}

func (i *Int128) UnmarshalJSON(data []byte) error {
var el Uint128
if err := json.Unmarshal(data, &el); err != nil {
return err
}

out := Int128(el)
*i = out

return nil
}

func (i *Float128) UnmarshalJSON(data []byte) error {
var el Uint128
if err := json.Unmarshal(data, &el); err != nil {
return err
}

out := Float128(el)
*i = out

return nil
}

func (i *Uint128) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
return nil
}

var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}

if !strings.HasPrefix(s, "0x") && !strings.HasPrefix(s, "0X") {
return fmt.Errorf("int128 expects 0x prefix")
}

truncatedVal := s[2:]
if len(truncatedVal) != 32 {
return fmt.Errorf("int128 expects 32 characters after 0x, had %d", len(truncatedVal))
}

loHex := truncatedVal[:16]
hiHex := truncatedVal[16:]

lo, err := hex.DecodeString(loHex)
if err != nil {
return err
}

hi, err := hex.DecodeString(hiHex)
if err != nil {
return err
}

loUint := binary.LittleEndian.Uint64(lo)
hiUint := binary.LittleEndian.Uint64(hi)

i.Lo = loUint
i.Hi = hiUint

return nil
}
Loading

0 comments on commit 91879e8

Please sign in to comment.