diff --git a/CHANGELOG.md b/CHANGELOG.md index eae55cb6..a6bc1fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Greatly improved performance of `NameToString` (`~230%`) method. - `TimePoint` will decode with `0` nanoseconds, when the `fitNodeos` flag is set on the ABI. - Ability to decode a `int128` and `uint128` in decimal format when `fitNodeos` flag is set on the ABI - Ability to decode nested `arrays` in ABI decoder. @@ -14,6 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `ActionTrace.ContextFree` field of type `bool` that was previously missing from the struct definition. ### Fixed +- Optional encoding of primitive types. + + A struct with a non-pointer type tagged with `eos:"optional"` is now properly encoded at the binary level. **Important** that means that for non-pointer type, when the value of the type is the "emtpy" value according to Golang rules, it will be written as not-present at the binary level. If it's something that you do want want, use a pointer to a primitive type. It's actually a good habit to use a pointer type for "optional" element anyway, to increase awarness. + +- Fix json tags for delegatebw action data. - Unpacking binary `Except` now correctly works. - Unpacking binary `Action` and `ActionTrace` now correctly works. - Unpacking binary `TransactionTrace` now correctly works. @@ -21,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unpacking binary `BlockState` now correctly works but is restricted to EOSIO 2.0.x version. ### Changed - - **BREAKING**: Fixed binary unpacking of `BlockState`, `TransactionTrace`, `SignedTransaction`, `Action` (and some inner types). This required changing a few struct fields to better fit with EOSIO definition, here the full list: +- **BREAKING**: Fixed binary unpacking of `BlockState`, `TransactionTrace`, `SignedTransaction`, `Action` (and some inner types). This required changing a few struct fields to better fit with EOSIO definition, here the full list: - `MerkleRoot.ActiveNodes` is now a `[]Checksum256`, was previously `[]string` - `MerkleRoot.NodeCount` is now a `uint64`, was previously `uint32` - Type `EOSNameOrUint32` has been removed and replaced by `PairAccountNameBlockNum` which is strictly typed now. diff --git a/abidecoder.go b/abidecoder.go index 9649512a..285b723e 100644 --- a/abidecoder.go +++ b/abidecoder.go @@ -42,8 +42,6 @@ func (a *ABI) Decode(binaryDecoder *Decoder, structName string) ([]byte, error) return a.decode(binaryDecoder, structName) } - - func (a *ABI) decode(binaryDecoder *Decoder, structName string) ([]byte, error) { if loggingEnabled { abiDecoderLog.Debug("decode struct", zap.String("name", structName)) @@ -104,7 +102,7 @@ func (a *ABI) resolveField(binaryDecoder *Decoder, fieldName, fieldType string, // check if this field is an alias aliasFieldType, isAlias := a.TypeNameForNewTypeName(fieldType) - if isAlias { + if isAlias { if loggingEnabled { abiDecoderLog.Debug("type is an alias", zap.String("from", fieldType), @@ -187,7 +185,7 @@ func (a *ABI) readArray(binaryDecoder *Decoder, fieldName, fieldType string, jso } // Decodes the EOS ABIs built-in types -func (a *ABI) read(binaryDecoder *Decoder, fieldName string, fieldType string, json []byte) ([]byte, error){ +func (a *ABI) read(binaryDecoder *Decoder, fieldName string, fieldType string, json []byte) ([]byte, error) { variant := a.VariantForName(fieldType) if variant != nil { variantIndex, err := binaryDecoder.ReadUvarint32() @@ -329,19 +327,13 @@ func (a *ABI) read(binaryDecoder *Decoder, fieldName string, fieldType string, j case "bool": if a.fitNodeos { value, err = binaryDecoder.ReadByte() - break + } else { + value, err = binaryDecoder.ReadBool() } - value, err = binaryDecoder.ReadBool() case "time_point": timePoint, e := binaryDecoder.ReadTimePoint() //todo double check if e == nil { - t := time.Unix(0, int64(timePoint*1000)) - if a.fitNodeos && t.Nanosecond() == 0{ - // nodeos time_point will contains the nano seconds even if they are 0 - value = fmt.Sprintf("%s.000", t.UTC().Format("2006-01-02T15:04:05")) - } else { - value = t.UTC().Format("2006-01-02T15:04:05.999") - } + value = formatTimePoint(timePoint, a.fitNodeos) } err = e case "time_point_sec": @@ -426,3 +418,12 @@ func analyzeFieldType(fieldType string) (typeName string, isOptional bool, isArr return fieldType, false, false, false } + +func formatTimePoint(timePoint TimePoint, shouldFitNodeos bool) string { + t := time.Unix(0, int64(timePoint*1000)) + format := "2006-01-02T15:04:05.999" + if shouldFitNodeos { + format = "2006-01-02T15:04:05.000" + } + return t.UTC().Format(format) +} diff --git a/abidecoder_test.go b/abidecoder_test.go index 0d5d67fb..c586dc50 100644 --- a/abidecoder_test.go +++ b/abidecoder_test.go @@ -1,32 +1,22 @@ package eos import ( + "bytes" "encoding/hex" "fmt" - "github.com/eoscanada/eos-go/ecc" "math" - "time" - + "strings" "testing" + "time" - + "github.com/eoscanada/eos-go/ecc" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - - "github.com/tidwall/gjson" - - "bytes" - - "strings" - - "github.com/stretchr/testify/assert" ) func TestABI_DecodeAction(t *testing.T) { - abiReader := strings.NewReader(abiString) - mockData := struct { BF1 string F1 Name @@ -233,8 +223,7 @@ func TestABI_decode_Float32FitNodeos(t *testing.T) { abi := &ABI{ fitNodeos: true, - Types: []ABIType{ - }, + Types: []ABIType{}, Structs: []StructDef{ { Name: "root", @@ -256,8 +245,7 @@ func TestABI_decode_Float32FitNodeos(t *testing.T) { func TestABI_decode_StructFieldTypeUint128(t *testing.T) { abi := &ABI{ - Types: []ABIType{ - }, + Types: []ABIType{}, Structs: []StructDef{ { Name: "root", @@ -284,11 +272,9 @@ func TestABI_decode_StructFieldTypeUint128(t *testing.T) { assert.JSONEq(t, `{"extern_amount":"84677000000000000000000"}`, string(json)) } - func TestABI_decode_StructFieldTypeTimePoint(t *testing.T) { abi := &ABI{ - Types: []ABIType{ - }, + Types: []ABIType{}, Structs: []StructDef{ { Name: "root", @@ -316,7 +302,6 @@ func TestABI_decode_StructFieldTypeTimePoint(t *testing.T) { } - func TestABI_decode_StructHasAliasedBase(t *testing.T) { abi := &ABI{ Types: []ABIType{ @@ -409,13 +394,12 @@ func TestABI_decode_StructFieldTypeHasArrayOfAliasArray(t *testing.T) { func TestABI_decode_StructFieldWithUint128(t *testing.T) { abi := &ABI{ fitNodeos: true, - Types: []ABIType{ - }, + Types: []ABIType{}, Structs: []StructDef{ { Name: "root", Fields: []FieldDef{ - {Name: "extern_amount", Type: "uint128" }, + {Name: "extern_amount", Type: "uint128"}, }, }, }, @@ -505,7 +489,7 @@ func TestABI_decode_StructFieldTypeHasAliasArray(t *testing.T) { func TestABI_decode_StructFieldHasAliasWithStructType(t *testing.T) { abi := &ABI{ - fitNodeos: true, + fitNodeos: true, Types: []ABIType{ ABIType{Type: "collab_data[]", NewTypeName: "approvals_t"}, }, @@ -824,7 +808,6 @@ func TestABI_decode_Uint8ArrayVec(t *testing.T) { }, } - json, err := abi.decode(NewDecoder(HexString("01020d13")), "endgame") require.NoError(t, err) assert.JSONEq(t, `{"player_hands": [[13,19]]}`, string(json)) @@ -909,8 +892,7 @@ func TestABI_decodeFieldsErr(t *testing.T) { func TestABI_decodeOptionalField(t *testing.T) { abi := &ABI{ - Types: []ABIType{ - }, + Types: []ABIType{}, Structs: []StructDef{ { Name: "root", @@ -929,40 +911,38 @@ func TestABI_decodeOptionalField(t *testing.T) { optionalNotPresent := &testOptionalField{ B: 0, } - optionalMissingFlag := struct {}{} + optionalMissingFlag := struct{}{} - tests := []struct{ - name string - data interface{} - fitNodeos bool - expectError bool + tests := []struct { + name string + data interface{} + fitNodeos bool + expectError bool expectedJSON string - }{ { - name: "optional present", - data: optional, + name: "optional present", + data: optional, expectedJSON: `{"field":"value.1"}`, }, { - name: "optional not present", - data: optionalNotPresent, + name: "optional not present", + data: optionalNotPresent, expectedJSON: ``, }, { - name: "optional not present fit nodeos", - fitNodeos: true, - data: optionalNotPresent, + name: "optional not present fit nodeos", + fitNodeos: true, + data: optionalNotPresent, expectedJSON: `{"field": null}`, }, { - name: "optional missing flag", - data: optionalMissingFlag, - expectError: true, + name: "optional missing flag", + data: optionalMissingFlag, + expectError: true, }, } - for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -987,8 +967,6 @@ func TestABI_decodeOptionalField(t *testing.T) { }) } - - //// Option is present //// Option is not present // @@ -1003,8 +981,7 @@ func TestABI_decodeOptionalField(t *testing.T) { func TestABI_decode_StructFieldArray(t *testing.T) { abi := &ABI{ - Types: []ABIType{ - }, + Types: []ABIType{}, Structs: []StructDef{ { Name: "root", @@ -1035,78 +1012,77 @@ func TestABI_Read(t *testing.T) { signatureBuffer, err := hex.DecodeString("001f69c3e7b2789bccd5c4be1030129f35e93de2e8e18468dca94c65600cac25b4636e5d75342499e5519a0df74c714fd5ad682662204068eff4ca9fac86254ae416") require.NoError(t, err) - tests := []struct{ - name string - fieldName string - typeName string - fitNodeos bool - data interface{} - expectError bool + tests := []struct { + name string + fieldName string + typeName string + fitNodeos bool + data interface{} + expectError bool expectedValue *string - }{ - {name: "string", fieldName: "testedField", typeName: "string", data: "this.is.a.test", expectedValue: s(`"this.is.a.test"`) }, - {name: "min int8", fieldName: "testedField", typeName: "int8", data: int8(-128), expectedValue: s("-128") }, - {name: "max int8", fieldName: "testedField", typeName: "int8", data: int8(127), expectedValue: s("127") }, - {name: "min uint8", fieldName: "testedField", typeName: "uint8", data: uint8(0), expectedValue: s("0") }, - {name: "max uint8", fieldName: "testedField", typeName: "uint8", data: uint8(255), expectedValue: s("255") }, - {name: "min int16", fieldName: "testedField", typeName: "int16", data: int16(-32768), expectedValue: s("-32768") }, - {name: "max int16", fieldName: "testedField", typeName: "int16", data: int16(32767), expectedValue: s("32767") }, - {name: "min uint16", fieldName: "testedField", typeName: "uint16", data: uint16(0), expectedValue: s("0") }, - {name: "max uint16", fieldName: "testedField", typeName: "uint16", data: uint16(65535), expectedValue: s("65535") }, - {name: "min int32", fieldName: "testedField", typeName: "int32", data: int32(-2147483648), expectedValue: s("-2147483648") }, - {name: "max int32", fieldName: "testedField", typeName: "int32", data: int32(2147483647), expectedValue: s("2147483647") }, - {name: "min uint32", fieldName: "testedField", typeName: "uint32", data: uint32(0), expectedValue: s("0") }, - {name: "max uint32", fieldName: "testedField", typeName: "uint32", data: uint32(4294967295), expectedValue: s("4294967295") }, - {name: "min int64", fieldName: "testedField", typeName: "int64", data: int64(-9223372036854775808), expectedValue: s(`"-9223372036854775808"`) }, - {name: "max int64", fieldName: "testedField", typeName: "int64", data: int64(9223372036854775807), expectedValue: s(`"9223372036854775807"`) }, - {name: "mid int64", fieldName: "testedField", typeName: "int64", data: int64(4096), expectedValue: s(`4096`) }, - {name: "stringified lower int64", fieldName: "testedField", typeName: "int64", data: int64(-5000000000), expectedValue: s(`"-5000000000"`) }, - {name: "min uint64", fieldName: "testedField", typeName: "uint64", data: uint64(0),expectedValue:s ("0") }, - {name: "max uint64", fieldName: "testedField", typeName: "uint64", data: uint64(18446744073709551615), expectedValue: s(`"18446744073709551615"`) }, - {name: "int128 1", fieldName: "testedField", typeName: "int128", data: Int128{Lo: 1, Hi: 0}, expectedValue: s(`"0x01000000000000000000000000000000"`) }, - {name: "int128 -1", fieldName: "testedField", typeName: "int128", data: Int128{Lo: math.MaxUint64, Hi: math.MaxUint64}, expectedValue: s(`"0xffffffffffffffffffffffffffffffff"`) }, - {name: "int128", fieldName: "testedField", typeName: "int128", data: Int128{Lo: 925, Hi: 125}, expectedValue: s(`"0x9d030000000000007d00000000000000"`) }, - {name: "int128 negative ", fieldName: "testedField", typeName: "int128", data: Int128{Lo: 1, Hi: math.MaxUint64}, expectedValue: s(`"0x0100000000000000ffffffffffffffff"`) }, - {name: "int128 fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "int128", data: Int128{Lo: 925, Hi: 125}, expectedValue: s(`"2305843009213693952925"`) }, - {name: "int128 negative fit nodeos ", fieldName: "testedField", fitNodeos: true, typeName: "int128", data: Int128{Lo: 1, Hi: math.MaxUint64}, expectedValue: s(`"-18446744073709551615"`) }, - {name: "uint128 1", fieldName: "testedField", typeName: "uint128", data: Int128{Lo: 1, Hi: 0}, expectedValue: s(`"0x01000000000000000000000000000000"`) }, - {name: "uint128", fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: 925, Hi: 125},expectedValue: s(`"0x9d030000000000007d00000000000000"`) }, - {name: "uint128 fit nodeos", fitNodeos: true,fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: 925, Hi: 125},expectedValue: s(`"2305843009213693952925"`) }, - {name: "uint128 max",fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: math.MaxUint64, Hi: math.MaxUint64},expectedValue: s(`"0xffffffffffffffffffffffffffffffff"`) }, - {name: "uint128 fit nodeos", fitNodeos: true,fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: math.MaxUint64, Hi: math.MaxUint64},expectedValue: s(`"340282366920938463463374607431768211455"`) }, - {name: "min varint32", fieldName: "testedField", typeName: "varint32", data: Varint32(-2147483648), expectedValue: s("-2147483648") }, - {name: "max varint32", fieldName: "testedField", typeName: "varint32", data: Varint32(2147483647), expectedValue:s ("2147483647") }, - {name: "min varuint32", fieldName: "testedField", typeName: "varuint32", data: Varuint32(0), expectedValue:s ("0") }, - {name: "max varuint32", fieldName: "testedField", typeName: "varuint32", data: Varuint32(4294967295), expectedValue:s ("4294967295") }, - {name: "min float 32", fieldName: "testedField", typeName: "float32", data: float32(math.SmallestNonzeroFloat32), expectedValue: s("0.000000000000000000000000000000000000000000001401298464324817") }, - {name: "max float 32", fieldName: "testedField", typeName: "float32", data: float32(math.MaxFloat32), expectedValue:s ("340282346638528860000000000000000000000") }, - {name: "min float64", fieldName: "testedField", typeName: "float64", data: math.SmallestNonzeroFloat64, expectedValue: s("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005") }, - {name: "max float64", fieldName: "testedField", typeName: "float64", data: math.MaxFloat64, expectedValue:s ("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") }, - {name: "float128", fieldName: "testedField", typeName: "float128", data: Float128{Lo: 1, Hi: 2}, expectedValue: s(`"0x01000000000000000200000000000000"`) }, - {name: "bool true", fieldName: "testedField", typeName: "bool", data: true, expectedValue: s("true") }, - {name: "bool false", fieldName: "testedField", typeName: "bool", data: false, expectedValue: s("false") }, - {name: "time_point", fieldName: "testedField", typeName: "time_point", data: TimePoint(1541085187001001), expectedValue: s(`"2018-11-01T15:13:07.001"`) }, - {name: "time_point_sec", fieldName: "testedField", typeName: "time_point_sec", data: TimePointSec(1681469753), expectedValue: s(`"2023-04-14T10:55:53"`) }, - {name: "block_timestamp_type", fieldName: "testedField", typeName: "block_timestamp_type", data: bt, expectedValue: s(`"2018-09-05T12:48:54"`) }, - {name: "Name", fieldName: "testedField", typeName: "name",data: Name("eoscanadacom"), expectedValue: s(`"eoscanadacom"`) }, - {name: "bytes", fieldName: "testedField", typeName: "bytes", data: []byte("this.is.a.test"), expectedValue: s(`"746869732e69732e612e74657374"`) }, - {name: "checksum160", fieldName: "testedField", typeName: "checksum160", data: Checksum160(make([]byte, TypeSize.Checksum160)), expectedValue: s(`"0000000000000000000000000000000000000000"`) }, - {name: "checksum256", fieldName: "testedField", typeName: "checksum256", data: Checksum256(make([]byte, TypeSize.Checksum256)), expectedValue: s(`"0000000000000000000000000000000000000000000000000000000000000000"`) }, - {name: "checksum512", fieldName: "testedField", typeName: "checksum512", data: Checksum512(make([]byte, TypeSize.Checksum512)), expectedValue: s(`"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`) }, - {name: "public_key", fieldName: "testedField", typeName: "public_key", data: ecc.MustNewPublicKey("EOS1111111111111111111111111111111114T1Anm"), expectedValue: s(`"EOS1111111111111111111111111111111114T1Anm"`) }, - {name: "public_key_wa", fieldName: "testedField", typeName: "public_key", data: ecc.MustNewPublicKey("PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj"), expectedValue: s(`"PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj"`) }, - {name: "signature", fieldName: "testedField", typeName: "signature", data: ecc.MustNewSignatureFromData(signatureBuffer), expectedValue: s(`"SIG_K1_K96L1au4xFJg5edn6qBK6UDbSsC2RKsMs4cXCA2LoCPZxBDMXehdZFWPh1GeRhzGoQjBwNK2eBmUXf4L8SBApL69pGdUJm"`) }, - {name: "signature_wa", fieldName: "testedField", typeName: "signature", data: ecc.MustNewSignature("SIG_WA_28AzYsRYSSA85Q4Jjp4zkiyBA8G85AcPsHU3HUuqLkY3LooYcFiSMGGxhEQcCzAhaZJqdaUXG16p8t63sDhqh9L4xc24CDxbf81D6FW4SXGjxQSM2D7FAJSSQCogjbqJanTP5CbSF8FWyaD4pVVAs4Z9ubqNhHCkiLDesEukwGYu6ujgwQkFqczow5cSwTqTirdgqCBjkGQLMT3KV2JwjN7b2qPAyDa2vvjsGWFP8HVTw2tctD6FBPHU9nFgtfcztkc3eqxVU9UbvUbKayU62dLZBwNCwHxmyPymH5YfoJLhBkS8s"), expectedValue: s(`"SIG_WA_28AzYsRYSSA85Q4Jjp4zkiyBA8G85AcPsHU3HUuqLkY3LooYcFiSMGGxhEQcCzAhaZJqdaUXG16p8t63sDhqh9L4xc24CDxbf81D6FW4SXGjxQSM2D7FAJSSQCogjbqJanTP5CbSF8FWyaD4pVVAs4Z9ubqNhHCkiLDesEukwGYu6ujgwQkFqczow5cSwTqTirdgqCBjkGQLMT3KV2JwjN7b2qPAyDa2vvjsGWFP8HVTw2tctD6FBPHU9nFgtfcztkc3eqxVU9UbvUbKayU62dLZBwNCwHxmyPymH5YfoJLhBkS8s"`) }, - {name: "symbol", fieldName: "testedField", typeName: "symbol", data: EOSSymbol, expectedValue: s(`"4,EOS"`) }, - {name: "symbol_code", fieldName: "testedField", typeName: "symbol_code", data: SymbolCode(22606239386324546), expectedValue: s(`"BNTDAPP"`) }, - {name: "asset", fieldName: "testedField", typeName: "asset", data: Asset{Amount: 100000, Symbol: EOSSymbol}, expectedValue: s(`"10.0000 EOS"`) }, - {name: "extended_asset", fieldName: "testedField", typeName: "extended_asset", data: ExtendedAsset{Asset: Asset{Amount: 10, Symbol: EOSSymbol}, Contract: "eoscanadacom"}, expectedValue: s("{\"quantity\":\"0.0010 EOS\",\"contract\":\"eoscanadacom\"}") }, - {name: "bad type", fieldName: "testedField", typeName: "bad.type.1", data: nil, expectedValue: nil, expectError: true}, - {name: "min float64 fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "float64", data: math.SmallestNonzeroFloat64, expectedValue: s(`"0.00000000000000000"`) }, - {name: "max float64 fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "float64", data: math.MaxFloat64, expectedValue: s(`"179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.00000000000000000"`) }, - {name: "bool true fit nodeos", fieldName: "testedField", fitNodeos: true,typeName: "bool", data: true, expectedValue: s("1") }, - {name: "bool false fit nodeos", fieldName: "testedField", fitNodeos: true,typeName: "bool", data: false, expectedValue: s("0") }, + {name: "string", fieldName: "testedField", typeName: "string", data: "this.is.a.test", expectedValue: s(`"this.is.a.test"`)}, + {name: "min int8", fieldName: "testedField", typeName: "int8", data: int8(-128), expectedValue: s("-128")}, + {name: "max int8", fieldName: "testedField", typeName: "int8", data: int8(127), expectedValue: s("127")}, + {name: "min uint8", fieldName: "testedField", typeName: "uint8", data: uint8(0), expectedValue: s("0")}, + {name: "max uint8", fieldName: "testedField", typeName: "uint8", data: uint8(255), expectedValue: s("255")}, + {name: "min int16", fieldName: "testedField", typeName: "int16", data: int16(-32768), expectedValue: s("-32768")}, + {name: "max int16", fieldName: "testedField", typeName: "int16", data: int16(32767), expectedValue: s("32767")}, + {name: "min uint16", fieldName: "testedField", typeName: "uint16", data: uint16(0), expectedValue: s("0")}, + {name: "max uint16", fieldName: "testedField", typeName: "uint16", data: uint16(65535), expectedValue: s("65535")}, + {name: "min int32", fieldName: "testedField", typeName: "int32", data: int32(-2147483648), expectedValue: s("-2147483648")}, + {name: "max int32", fieldName: "testedField", typeName: "int32", data: int32(2147483647), expectedValue: s("2147483647")}, + {name: "min uint32", fieldName: "testedField", typeName: "uint32", data: uint32(0), expectedValue: s("0")}, + {name: "max uint32", fieldName: "testedField", typeName: "uint32", data: uint32(4294967295), expectedValue: s("4294967295")}, + {name: "min int64", fieldName: "testedField", typeName: "int64", data: int64(-9223372036854775808), expectedValue: s(`"-9223372036854775808"`)}, + {name: "max int64", fieldName: "testedField", typeName: "int64", data: int64(9223372036854775807), expectedValue: s(`"9223372036854775807"`)}, + {name: "mid int64", fieldName: "testedField", typeName: "int64", data: int64(4096), expectedValue: s(`4096`)}, + {name: "stringified lower int64", fieldName: "testedField", typeName: "int64", data: int64(-5000000000), expectedValue: s(`"-5000000000"`)}, + {name: "min uint64", fieldName: "testedField", typeName: "uint64", data: uint64(0), expectedValue: s("0")}, + {name: "max uint64", fieldName: "testedField", typeName: "uint64", data: uint64(18446744073709551615), expectedValue: s(`"18446744073709551615"`)}, + {name: "int128 1", fieldName: "testedField", typeName: "int128", data: Int128{Lo: 1, Hi: 0}, expectedValue: s(`"0x01000000000000000000000000000000"`)}, + {name: "int128 -1", fieldName: "testedField", typeName: "int128", data: Int128{Lo: math.MaxUint64, Hi: math.MaxUint64}, expectedValue: s(`"0xffffffffffffffffffffffffffffffff"`)}, + {name: "int128", fieldName: "testedField", typeName: "int128", data: Int128{Lo: 925, Hi: 125}, expectedValue: s(`"0x9d030000000000007d00000000000000"`)}, + {name: "int128 negative ", fieldName: "testedField", typeName: "int128", data: Int128{Lo: 1, Hi: math.MaxUint64}, expectedValue: s(`"0x0100000000000000ffffffffffffffff"`)}, + {name: "int128 fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "int128", data: Int128{Lo: 925, Hi: 125}, expectedValue: s(`"2305843009213693952925"`)}, + {name: "int128 negative fit nodeos ", fieldName: "testedField", fitNodeos: true, typeName: "int128", data: Int128{Lo: 1, Hi: math.MaxUint64}, expectedValue: s(`"-18446744073709551615"`)}, + {name: "uint128 1", fieldName: "testedField", typeName: "uint128", data: Int128{Lo: 1, Hi: 0}, expectedValue: s(`"0x01000000000000000000000000000000"`)}, + {name: "uint128", fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: 925, Hi: 125}, expectedValue: s(`"0x9d030000000000007d00000000000000"`)}, + {name: "uint128 fit nodeos", fitNodeos: true, fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: 925, Hi: 125}, expectedValue: s(`"2305843009213693952925"`)}, + {name: "uint128 max", fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: math.MaxUint64, Hi: math.MaxUint64}, expectedValue: s(`"0xffffffffffffffffffffffffffffffff"`)}, + {name: "uint128 fit nodeos", fitNodeos: true, fieldName: "testedField", typeName: "uint128", data: Uint128{Lo: math.MaxUint64, Hi: math.MaxUint64}, expectedValue: s(`"340282366920938463463374607431768211455"`)}, + {name: "min varint32", fieldName: "testedField", typeName: "varint32", data: Varint32(-2147483648), expectedValue: s("-2147483648")}, + {name: "max varint32", fieldName: "testedField", typeName: "varint32", data: Varint32(2147483647), expectedValue: s("2147483647")}, + {name: "min varuint32", fieldName: "testedField", typeName: "varuint32", data: Varuint32(0), expectedValue: s("0")}, + {name: "max varuint32", fieldName: "testedField", typeName: "varuint32", data: Varuint32(4294967295), expectedValue: s("4294967295")}, + {name: "min float 32", fieldName: "testedField", typeName: "float32", data: float32(math.SmallestNonzeroFloat32), expectedValue: s("0.000000000000000000000000000000000000000000001401298464324817")}, + {name: "max float 32", fieldName: "testedField", typeName: "float32", data: float32(math.MaxFloat32), expectedValue: s("340282346638528860000000000000000000000")}, + {name: "min float64", fieldName: "testedField", typeName: "float64", data: math.SmallestNonzeroFloat64, expectedValue: s("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005")}, + {name: "max float64", fieldName: "testedField", typeName: "float64", data: math.MaxFloat64, expectedValue: s("179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")}, + {name: "float128", fieldName: "testedField", typeName: "float128", data: Float128{Lo: 1, Hi: 2}, expectedValue: s(`"0x01000000000000000200000000000000"`)}, + {name: "bool true", fieldName: "testedField", typeName: "bool", data: true, expectedValue: s("true")}, + {name: "bool false", fieldName: "testedField", typeName: "bool", data: false, expectedValue: s("false")}, + {name: "time_point", fieldName: "testedField", typeName: "time_point", data: TimePoint(1541085187001001), expectedValue: s(`"2018-11-01T15:13:07.001"`)}, + {name: "time_point_sec", fieldName: "testedField", typeName: "time_point_sec", data: TimePointSec(1681469753), expectedValue: s(`"2023-04-14T10:55:53"`)}, + {name: "block_timestamp_type", fieldName: "testedField", typeName: "block_timestamp_type", data: bt, expectedValue: s(`"2018-09-05T12:48:54"`)}, + {name: "Name", fieldName: "testedField", typeName: "name", data: Name("eoscanadacom"), expectedValue: s(`"eoscanadacom"`)}, + {name: "bytes", fieldName: "testedField", typeName: "bytes", data: []byte("this.is.a.test"), expectedValue: s(`"746869732e69732e612e74657374"`)}, + {name: "checksum160", fieldName: "testedField", typeName: "checksum160", data: Checksum160(make([]byte, TypeSize.Checksum160)), expectedValue: s(`"0000000000000000000000000000000000000000"`)}, + {name: "checksum256", fieldName: "testedField", typeName: "checksum256", data: Checksum256(make([]byte, TypeSize.Checksum256)), expectedValue: s(`"0000000000000000000000000000000000000000000000000000000000000000"`)}, + {name: "checksum512", fieldName: "testedField", typeName: "checksum512", data: Checksum512(make([]byte, TypeSize.Checksum512)), expectedValue: s(`"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`)}, + {name: "public_key", fieldName: "testedField", typeName: "public_key", data: ecc.MustNewPublicKey("EOS1111111111111111111111111111111114T1Anm"), expectedValue: s(`"EOS1111111111111111111111111111111114T1Anm"`)}, + {name: "public_key_wa", fieldName: "testedField", typeName: "public_key", data: ecc.MustNewPublicKey("PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj"), expectedValue: s(`"PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj"`)}, + {name: "signature", fieldName: "testedField", typeName: "signature", data: ecc.MustNewSignatureFromData(signatureBuffer), expectedValue: s(`"SIG_K1_K96L1au4xFJg5edn6qBK6UDbSsC2RKsMs4cXCA2LoCPZxBDMXehdZFWPh1GeRhzGoQjBwNK2eBmUXf4L8SBApL69pGdUJm"`)}, + {name: "signature_wa", fieldName: "testedField", typeName: "signature", data: ecc.MustNewSignature("SIG_WA_28AzYsRYSSA85Q4Jjp4zkiyBA8G85AcPsHU3HUuqLkY3LooYcFiSMGGxhEQcCzAhaZJqdaUXG16p8t63sDhqh9L4xc24CDxbf81D6FW4SXGjxQSM2D7FAJSSQCogjbqJanTP5CbSF8FWyaD4pVVAs4Z9ubqNhHCkiLDesEukwGYu6ujgwQkFqczow5cSwTqTirdgqCBjkGQLMT3KV2JwjN7b2qPAyDa2vvjsGWFP8HVTw2tctD6FBPHU9nFgtfcztkc3eqxVU9UbvUbKayU62dLZBwNCwHxmyPymH5YfoJLhBkS8s"), expectedValue: s(`"SIG_WA_28AzYsRYSSA85Q4Jjp4zkiyBA8G85AcPsHU3HUuqLkY3LooYcFiSMGGxhEQcCzAhaZJqdaUXG16p8t63sDhqh9L4xc24CDxbf81D6FW4SXGjxQSM2D7FAJSSQCogjbqJanTP5CbSF8FWyaD4pVVAs4Z9ubqNhHCkiLDesEukwGYu6ujgwQkFqczow5cSwTqTirdgqCBjkGQLMT3KV2JwjN7b2qPAyDa2vvjsGWFP8HVTw2tctD6FBPHU9nFgtfcztkc3eqxVU9UbvUbKayU62dLZBwNCwHxmyPymH5YfoJLhBkS8s"`)}, + {name: "symbol", fieldName: "testedField", typeName: "symbol", data: EOSSymbol, expectedValue: s(`"4,EOS"`)}, + {name: "symbol_code", fieldName: "testedField", typeName: "symbol_code", data: SymbolCode(22606239386324546), expectedValue: s(`"BNTDAPP"`)}, + {name: "asset", fieldName: "testedField", typeName: "asset", data: Asset{Amount: 100000, Symbol: EOSSymbol}, expectedValue: s(`"10.0000 EOS"`)}, + {name: "extended_asset", fieldName: "testedField", typeName: "extended_asset", data: ExtendedAsset{Asset: Asset{Amount: 10, Symbol: EOSSymbol}, Contract: "eoscanadacom"}, expectedValue: s("{\"quantity\":\"0.0010 EOS\",\"contract\":\"eoscanadacom\"}")}, + {name: "bad type", fieldName: "testedField", typeName: "bad.type.1", data: nil, expectedValue: nil, expectError: true}, + {name: "min float64 fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "float64", data: math.SmallestNonzeroFloat64, expectedValue: s(`"0.00000000000000000"`)}, + {name: "max float64 fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "float64", data: math.MaxFloat64, expectedValue: s(`"179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.00000000000000000"`)}, + {name: "bool true fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "bool", data: true, expectedValue: s("1")}, + {name: "bool false fit nodeos", fieldName: "testedField", fitNodeos: true, typeName: "bool", data: false, expectedValue: s("0")}, } for _, c := range tests { @@ -1120,7 +1096,7 @@ func TestABI_Read(t *testing.T) { if c.expectedValue != nil { value = *c.expectedValue } - require.NoError(t, err, fmt.Sprintf("encoding value %s, of type %s",value , c.typeName), c.name) + require.NoError(t, err, fmt.Sprintf("encoding value %s, of type %s", value, c.typeName), c.name) abi := ABI{} abi.fitNodeos = c.fitNodeos @@ -1191,6 +1167,73 @@ func TestABIDecoder_analyseFieldType(t *testing.T) { } } +func Test_formatTimePoint(t *testing.T) { + tests := []struct { + name string + time TimePoint + shouldFitNodeos bool + expectedOutput string + }{ + { + name: "golden path with fit nodeos", + time: 1588450213523000, + shouldFitNodeos: false, + expectedOutput: "2020-05-02T20:10:13.523", + }, + { + name: "golden path without fit nodeos", + time: 1588450213523000, + shouldFitNodeos: false, + expectedOutput: "2020-05-02T20:10:13.523", + }, + { + name: "0 nano second with fit nodeos", + time: 1568822400000000, + shouldFitNodeos: true, + expectedOutput: "2019-09-18T16:00:00.000", + }, + { + name: "0 nano second without fit nodeos", + time: 1568822400000000, + shouldFitNodeos: false, + expectedOutput: "2019-09-18T16:00:00", + }, + { + name: "500 nano second with fit nodeos", + time: 1588450213500000, + shouldFitNodeos: true, + expectedOutput: "2020-05-02T20:10:13.500", + }, + { + name: "500 nano second without fit nodeos", + time: 1588450213500000, + shouldFitNodeos: false, + expectedOutput: "2020-05-02T20:10:13.5", + }, + { + name: "520 nano second with fit nodeos", + time: 1588450213520000, + shouldFitNodeos: true, + expectedOutput: "2020-05-02T20:10:13.520", + }, + { + name: "520 nano second without fit nodeos", + time: 1588450213520000, + shouldFitNodeos: false, + expectedOutput: "2020-05-02T20:10:13.52", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + time := formatTimePoint(test.time, test.shouldFitNodeos) + fmt.Println(time) + assert.Equal(t, test.expectedOutput, time) + }) + } + +} + func HexString(input string) []byte { buffer, err := hex.DecodeString(input) if err != nil { diff --git a/decoder.go b/decoder.go index d8f58dcf..a28b617a 100644 --- a/decoder.go +++ b/decoder.go @@ -10,6 +10,7 @@ import ( "reflect" "strings" "time" + "unicode/utf8" "github.com/eoscanada/eos-go/ecc" "go.uber.org/zap" @@ -229,6 +230,7 @@ func (d *Decoder) Decode(v interface{}, options ...DecodeOption) (err error) { } rv.SetString(name) return + case *byte, *P2PMessageType, *TransactionStatus, *CompressionType, *IDListMode, *GoAwayReason: var n byte n, err = d.ReadByte() @@ -795,7 +797,6 @@ func (d *Decoder) ReadNodeosFloat32() (out float32, err error) { return } - func (d *Decoder) ReadFloat64() (out float64, err error) { if d.remaining() < TypeSize.Float64 { err = fmt.Errorf("float64 required [%d] bytes, remaining [%d]", TypeSize.Float64, d.remaining()) @@ -811,6 +812,21 @@ func (d *Decoder) ReadFloat64() (out float64, err error) { return } +func fixUtf(r rune) rune { + if r == utf8.RuneError { + return '�' + } + return r +} +func (d *Decoder) SafeReadUTF8String() (out string, err error) { + data, err := d.ReadByteArray() + out = strings.Map(fixUtf, string(data)) + if loggingEnabled { + decoderLog.Debug("read safe UTF8 string", zap.String("val", out)) + } + return +} + func (d *Decoder) ReadString() (out string, err error) { data, err := d.ReadByteArray() out = string(data) diff --git a/decoder_test.go b/decoder_test.go index 23b2bde5..f6f6b647 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "encoding/hex" "encoding/json" - "fmt" "io/ioutil" "testing" "time" @@ -399,8 +398,6 @@ func TestDecoder_BlockState(t *testing.T) { json, err := json.MarshalIndent(blockState, "", " ") require.NoError(t, err) - fmt.Println(string(json)) - expected, err := ioutil.ReadFile(test.expectedJSONFile) require.NoError(t, err) diff --git a/encoder.go b/encoder.go index be86cd6d..d6a29f0e 100644 --- a/encoder.go +++ b/encoder.go @@ -20,7 +20,7 @@ import ( // // **Warning** This is experimental, exposed only for internal usage for now. type MarshalerBinary interface { - MarshalerBinary(encoder *Encoder) error + MarshalBinary(encoder *Encoder) error } func MarshalBinary(v interface{}) ([]byte, error) { @@ -58,13 +58,15 @@ func (e *Encoder) writeName(name Name) error { func (e *Encoder) Encode(v interface{}) (err error) { switch cv := v.(type) { case MarshalerBinary: - return cv.MarshalerBinary(e) + return cv.MarshalBinary(e) case BaseVariant: err = e.writeUVarInt(int(cv.TypeID)) if err != nil { return } return e.Encode(cv.Impl) + case SafeString: + return e.writeString(string(cv)) case Name: return e.writeName(cv) case AccountName: @@ -106,8 +108,6 @@ func (e *Encoder) Encode(v interface{}) (err error) { return e.writeUint64(cv) case Int64: return e.writeUint64(uint64(cv)) - case Uint64: - return e.writeUint64(uint64(cv)) case int64: return e.writeInt64(cv) case float32: @@ -234,7 +234,7 @@ func (e *Encoder) Encode(v interface{}) (err error) { if v.CanInterface() { isPresent := true if tag == "optional" { - isPresent = !v.IsNil() + isPresent = !v.IsZero() e.writeBool(isPresent) } diff --git a/encoder_test.go b/encoder_test.go index da1801d0..87912084 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -29,3 +30,40 @@ func TestEncoder_MapStringString(t *testing.T) { require.Fail(t, "encoded map is invalid", "must be either %q or %q, got %q", expected1, expected2, out) } } + +func Test_OptionalPrimitiveType(t *testing.T) { + type test struct { + ID uint64 `eos:"optional"` + } + + out, err := MarshalBinary(test{ID: 0}) + require.NoError(t, err) + + assert.Equal(t, []byte{0x0}, out) + + out, err = MarshalBinary(test{ID: 10}) + require.NoError(t, err) + + assert.Equal(t, []byte{0x1, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, out) +} + +func Test_OptionalPointerToPrimitiveType(t *testing.T) { + type test struct { + ID *Uint64 `eos:"optional"` + } + + out, err := MarshalBinary(test{ID: nil}) + require.NoError(t, err) + assert.Equal(t, []byte{0x0}, out) + + id := Uint64(0) + out, err = MarshalBinary(test{ID: &id}) + require.NoError(t, err) + assert.Equal(t, []byte{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, out) + + id = Uint64(10) + out, err = MarshalBinary(test{ID: &id}) + require.NoError(t, err) + + assert.Equal(t, []byte{0x1, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, out) +} diff --git a/name.go b/name.go index eebb38e5..b53a6a1c 100644 --- a/name.go +++ b/name.go @@ -77,7 +77,19 @@ func charToSymbol(c byte) byte { var base32Alphabet = []byte(".12345abcdefghijklmnopqrstuvwxyz") +var eosioNameUint64 = uint64(6138663577826885632) +var eosioTokenNameUint64 = uint64(6138663591592764928) +var cachedNames = map[uint64]string{ + 6138663577826885632: "eosio", + 6138663591592764928: "eosio.token", +} + func NameToString(in uint64) string { + // Some particularly used name are pre-cached, so we avoid the transformation altogether, and reduce memory usage + if name, found := cachedNames[in]; found { + return name + } + // ported from libraries/chain/name.cpp in eosio a := []byte{'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.'} @@ -99,5 +111,25 @@ func NameToString(in uint64) string { tmp >>= shift } - return strings.TrimRight(string(a), ".") + // We had a call to `strings.TrimRight` before, but that was causing lots of + // allocation and lost CPU cycles. We now have our own cutting method that + // improves performance a lot. + return trimRightDots(a) +} + +func trimRightDots(bytes []byte) string { + trimUpTo := -1 + for i := 12; i >= 0; i-- { + if bytes[i] == '.' { + trimUpTo = i + } else { + break + } + } + + if trimUpTo == -1 { + return string(bytes) + } + + return string(bytes[0:trimUpTo]) } diff --git a/responses.go b/responses.go index aba0f993..75be64f8 100644 --- a/responses.go +++ b/responses.go @@ -104,7 +104,7 @@ type ActionTrace struct { Action *Action `json:"act"` ContextFree bool `json:"context_free"` Elapsed Int64 `json:"elapsed"` - Console string `json:"console"` + Console SafeString `json:"console"` TransactionID Checksum256 `json:"trx_id"` BlockNum uint32 `json:"block_num"` BlockTime BlockTimestamp `json:"block_time"` @@ -326,7 +326,7 @@ type TransactionProcessed struct { type Trace struct { Receiver AccountName `json:"receiver"` // Action Action `json:"act"` // FIXME: how do we unpack that ? what's on the other side anyway? - Console string `json:"console"` + Console SafeString `json:"console"` DataAccess []DataAccess `json:"data_access"` } diff --git a/ship/types.go b/ship/types.go index ac6b8371..f61d1417 100644 --- a/ship/types.go +++ b/ship/types.go @@ -64,7 +64,7 @@ type ActionTraceV0 struct { Act *Action ContextFree bool Elapsed int64 - Console string + Console eos.SafeString AccountRamDeltas []*eos.AccountRAMDelta Except string `eos:"optional"` ErrorCode uint64 `eos:"optional"` diff --git a/system/delegatebw.go b/system/delegatebw.go index c9706edd..7740ebde 100644 --- a/system/delegatebw.go +++ b/system/delegatebw.go @@ -27,7 +27,7 @@ func NewDelegateBW(from, receiver eos.AccountName, stakeCPU, stakeNet eos.Asset, type DelegateBW struct { From eos.AccountName `json:"from"` Receiver eos.AccountName `json:"receiver"` - StakeNet eos.Asset `json:"stake_net"` - StakeCPU eos.Asset `json:"stake_cpu"` + StakeNet eos.Asset `json:"stake_net_quantity"` + StakeCPU eos.Asset `json:"stake_cpu_quantity"` Transfer eos.Bool `json:"transfer"` } diff --git a/types.go b/types.go index 348f3606..eda9def7 100644 --- a/types.go +++ b/types.go @@ -35,6 +35,18 @@ func AN(in string) AccountName { return AccountName(in) } func ActN(in string) ActionName { return ActionName(in) } func PN(in string) PermissionName { return PermissionName(in) } +type SafeString string + +func (ss *SafeString) UnmarshalBinary(d *Decoder) error { + s, e := d.SafeReadUTF8String() + if e != nil { + return e + } + + *ss = SafeString(s) + return nil +} + type AccountResourceLimit struct { Used Int64 `json:"used"` Available Int64 `json:"available"` @@ -951,6 +963,10 @@ func (i *Uint64) UnmarshalJSON(data []byte) error { return nil } +func (i Uint64) MarshalBinary(encoder *Encoder) error { + return encoder.writeUint64(uint64(i)) +} + // uint128 type Uint128 struct { Lo uint64 @@ -1256,7 +1272,7 @@ func (a fcVariant) ToNative() interface{} { } if a.TypeID == fcVariantInt64Type { - return Int64(a.Impl.(uint64)) + return Int64(a.Impl.(int64)) } if a.TypeID == fcVariantUint64Type { diff --git a/types_test.go b/types_test.go index 76241159..10ae9956 100644 --- a/types_test.go +++ b/types_test.go @@ -1,6 +1,7 @@ package eos import ( + "bytes" "encoding/binary" "encoding/hex" "encoding/json" @@ -21,6 +22,31 @@ func TestChecksum256String(t *testing.T) { assert.Equal(t, "01020304", s.String()) } +func TestSafeString(t *testing.T) { + const nonUTF8 = "\xca\xc0\x20\xbd\xe7\x0a" + filtered := strings.Map(fixUtf, nonUTF8) + + require.NotEqual(t, filtered, nonUTF8) + + buf := new(bytes.Buffer) + enc := NewEncoder(buf) + enc.writeString(nonUTF8) + + d := NewDecoder(buf.Bytes()) + var ss SafeString + + err := d.Decode(&ss) + require.NoError(t, err) + assert.Equal(t, SafeString(filtered), ss, "SafeString should contain filtered data") + + d = NewDecoder(buf.Bytes()) + var s string + err = d.Decode(&s) + require.NoError(t, err) + assert.Equal(t, nonUTF8, s, "string should return unfiltered data") + +} + func TestUint128JSONUnmarshal(t *testing.T) { tests := []struct { name string @@ -112,40 +138,40 @@ func TestUint128JSONUnmarshal(t *testing.T) { } func Test_twosComplement(t *testing.T) { - tests := []struct{ + tests := []struct { name string input []byte expectOutput []byte }{ { - name: "-1", + name: "-1", // 0xffffffffffffffffffffffffffffffff - input: []byte{0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, + input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // 0x00000000000000000000000000000001 // the current algorithm will simply omit MSB 0's expectOutput: []byte{0x01}, }, { - name: "-18446744073709551615", + name: "-18446744073709551615", // 0xffffffffffffffff0000000000000001 - input: []byte{0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}, + input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, // 0x0000000000000000ffffffffffffffff // the current algorithm will simply omit MSB 0's - expectOutput: []byte{0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, + expectOutput: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }, { - name: "-170141183460469231731687303715884105727", + name: "-170141183460469231731687303715884105727", // 0x80000000000000000000000000000001 - input: []byte{0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}, + input: []byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, // 0x7fffffffffffffffffffffffffffffff - expectOutput: []byte{0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, + expectOutput: []byte{0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }, { - name: "-170141183460469231731687303715884105728", + name: "-170141183460469231731687303715884105728", // 0x80000000000000000000000000000000 - input:[]byte{0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + input: []byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x80000000000000000000000000000000 - expectOutput: []byte{0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + expectOutput: []byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }, } @@ -158,11 +184,11 @@ func Test_twosComplement(t *testing.T) { func TestInt128JSONUnmarshal(t *testing.T) { tests := []struct { - name string - input string - expectedLo uint64 - expectedHi uint64 - expectedError string + name string + input string + expectedLo uint64 + expectedHi uint64 + expectedError string expectedDecimal string }{ { @@ -181,66 +207,66 @@ func TestInt128JSONUnmarshal(t *testing.T) { expectedError: "encoding/hex: invalid byte: U+006D 'm'", }, { - name: "zero", - input: `"0x00000000000000000000000000000000"`, - expectedLo: 0, - expectedHi: 0, + name: "zero", + input: `"0x00000000000000000000000000000000"`, + expectedLo: 0, + expectedHi: 0, expectedDecimal: "0", }, { - name: "one", - input: `"0x01000000000000000000000000000000"`, - expectedLo: 1, - expectedHi: 0, + name: "one", + input: `"0x01000000000000000000000000000000"`, + expectedLo: 1, + expectedHi: 0, expectedDecimal: "1", }, { - name: "negative one", - input: `"0xffffffffffffffffffffffffffffffff"`, - expectedLo: math.MaxUint64, - expectedHi: math.MaxUint64, + name: "negative one", + input: `"0xffffffffffffffffffffffffffffffff"`, + expectedLo: math.MaxUint64, + expectedHi: math.MaxUint64, expectedDecimal: "-1", }, { - name: "max uint64", - input: `"0xffffffffffffffff0000000000000000"`, - expectedLo: math.MaxUint64, - expectedHi: 0, + name: "max uint64", + input: `"0xffffffffffffffff0000000000000000"`, + expectedLo: math.MaxUint64, + expectedHi: 0, expectedDecimal: "18446744073709551615", }, { - name: "negative max uint64", - input: `"0x0100000000000000ffffffffffffffff"`, - expectedLo: 1, - expectedHi: math.MaxUint64, + name: "negative max uint64", + input: `"0x0100000000000000ffffffffffffffff"`, + expectedLo: 1, + expectedHi: math.MaxUint64, expectedDecimal: "-18446744073709551615", }, { - name: "largest positive number", - input: `"0xffffffffffffffffffffffffffffff7f"`, - expectedLo: math.MaxUint64, - expectedHi: 0x7fffffffffffffff, //9223372036854775807 + name: "largest positive number", + input: `"0xffffffffffffffffffffffffffffff7f"`, + expectedLo: math.MaxUint64, + expectedHi: 0x7fffffffffffffff, //9223372036854775807 expectedDecimal: "170141183460469231731687303715884105727", }, { - name: "before smallest negative number", - input: `"0x01000000000000000000000000000080"`, - expectedLo: 1, - expectedHi: 0x8000000000000000, //9223372036854775808 + name: "before smallest negative number", + input: `"0x01000000000000000000000000000080"`, + expectedLo: 1, + expectedHi: 0x8000000000000000, //9223372036854775808 expectedDecimal: "-170141183460469231731687303715884105727", }, { - name: "smallest negative number", - input: `"0x00000000000000000000000000000080"`, - expectedLo: 0, - expectedHi: 0x8000000000000000, + name: "smallest negative number", + input: `"0x00000000000000000000000000000080"`, + expectedLo: 0, + expectedHi: 0x8000000000000000, expectedDecimal: "-170141183460469231731687303715884105728", }, { - name: "value from nodeos serialization", - input: `"0x9d030000000000007d00000000000000"`, - expectedLo: 925, - expectedHi: 125, + name: "value from nodeos serialization", + input: `"0x9d030000000000007d00000000000000"`, + expectedLo: 925, + expectedHi: 125, expectedDecimal: "2305843009213693952925", }, } @@ -348,7 +374,7 @@ func TestSimplePacking(t *testing.T) { } cnt, err := MarshalBinary(&M{ Acct: AccountName("bob"), - A: []*S{&S{"hello"}, &S{"world"}}, + A: []*S{{"hello"}, {"world"}}, }) require.NoError(t, err) @@ -456,6 +482,8 @@ func TestNameToString(t *testing.T) { in string out string }{ + {"0000000000000000", ""}, + {"0000000000003055", "eos"}, {"0000001e4d75af46", "currency"}, {"0000000000ea3055", "eosio"}, {"00409e9a2264b89a", "newaccount"}, @@ -468,7 +496,6 @@ func TestNameToString(t *testing.T) { {"0000000080ab26a7", "owner"}, {"00000040258ab2c2", "setcode"}, {"00000000b863b2c2", "setabi"}, - {"00000000b863b2c2", "setabi"}, } for _, test := range tests { @@ -479,6 +506,23 @@ func TestNameToString(t *testing.T) { } } +func BenchmarkNameToString(b *testing.B) { + for i := 0; i < b.N; i++ { + NameToString(5093418677655568384) + NameToString(6138663577826885632) + NameToString(11148770977341390848) + NameToString(14542491017828892672) + NameToString(3617214756542218240) + NameToString(14829575313431724032) + NameToString(3596594555622785024) + NameToString(15335505127214321600) + NameToString(15371467950649982976) + NameToString(12044502819693133824) + NameToString(14029427681804681216) + NameToString(14029385431137648640) + } +} + func TestPackAccountName(t *testing.T) { // SHOULD IMPLEMENT: string_to_name from contracts/eosiolib/types.hpp tests := []struct { @@ -519,7 +563,7 @@ func TestAuthorityBinaryMarshal(t *testing.T) { a := Authority{ Threshold: 2, Keys: []KeyWeight{ - KeyWeight{ + { PublicKey: key, Weight: 5, },