From 7c2d912b10e875c533bf5893ee4c4fb2674c9963 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 13 Aug 2024 23:00:58 -0400 Subject: [PATCH 1/8] Add utils for compact RSV encoding/decoding Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 1 + pkg/secp256k1/keypair.go | 3 +-- pkg/secp256k1/keypair_test.go | 11 +++++++++++ pkg/secp256k1/signer.go | 23 +++++++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index 4528d4e..95d832c 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -103,4 +103,5 @@ var ( MsgInvalidEIP1559Transaction = ffe("FF22084", "Transaction payload invalid (EIP-1559): %v") MsgInvalidEIP155TransactionV = ffe("FF22085", "Invalid V value from EIP-155 transaction (chainId=%d)") MsgInvalidChainID = ffe("FF22086", "Invalid chainId expected=%d actual=%d") + MsgSigningInvalidCompactRSV = ffe("FF22087", "Invalid signature data (compact R,S,V) length=%d (expected=65)") ) diff --git a/pkg/secp256k1/keypair.go b/pkg/secp256k1/keypair.go index a21e79e..8901d58 100644 --- a/pkg/secp256k1/keypair.go +++ b/pkg/secp256k1/keypair.go @@ -45,8 +45,7 @@ func GenerateSecp256k1KeyPair() (*KeyPair, error) { // Deprecated: Note there is no error condition returned by this function (use KeyPairFromBytes) func NewSecp256k1KeyPair(b []byte) (*KeyPair, error) { - key, pubKey := btcec.PrivKeyFromBytes(b) - return wrapSecp256k1Key(key, pubKey), nil + return KeyPairFromBytes(b), nil } func KeyPairFromBytes(b []byte) *KeyPair { diff --git a/pkg/secp256k1/keypair_test.go b/pkg/secp256k1/keypair_test.go index 0f1cc78..18bfc22 100644 --- a/pkg/secp256k1/keypair_test.go +++ b/pkg/secp256k1/keypair_test.go @@ -17,6 +17,7 @@ package secp256k1 import ( + "context" "math/big" "testing" @@ -57,6 +58,16 @@ func TestGeneratedKeyRoundTrip(t *testing.T) { assert.NoError(t, err) assert.Equal(t, keypair.Address, *addr) + sigRSV := sig.CompactRSV() + sig2, err := DecodeCompactRSV(context.Background(), sigRSV) + assert.NoError(t, err) + addr, err = sig2.Recover(data, 1001) + assert.NoError(t, err) + assert.Equal(t, keypair.Address, *addr) + + _, err = DecodeCompactRSV(context.Background(), []byte("wrong")) + assert.Regexp(t, "FF22087", err) + _, err = sig.Recover(data, 42) assert.Regexp(t, "invalid V value in signature", err) diff --git a/pkg/secp256k1/signer.go b/pkg/secp256k1/signer.go index 0ef6109..b5f5c1e 100644 --- a/pkg/secp256k1/signer.go +++ b/pkg/secp256k1/signer.go @@ -17,10 +17,13 @@ package secp256k1 import ( + "context" "fmt" "math/big" ecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-signer/internal/signermsgs" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "golang.org/x/crypto/sha3" ) @@ -99,6 +102,26 @@ func (s *SignatureData) RecoverDirect(message []byte, chainID int64) (a *ethtype return PublicKeyToAddress(pubKey), nil } +// We use the ethereum convention of R,S,V for compact packing (mentioned because Golang tends to prefer V,R,S) +func (s *SignatureData) CompactRSV() []byte { + signatureBytes := make([]byte, 65) + s.R.FillBytes(signatureBytes[0:32]) + s.S.FillBytes(signatureBytes[32:64]) + signatureBytes[64] = byte(s.V.Int64()) + return signatureBytes +} + +func DecodeCompactRSV(ctx context.Context, compactRSV []byte) (*SignatureData, error) { + if len(compactRSV) != 65 { + return nil, i18n.NewError(ctx, signermsgs.MsgSigningInvalidCompactRSV, len(compactRSV)) + } + var sig SignatureData + sig.R = new(big.Int).SetBytes(compactRSV[0:32]) + sig.S = new(big.Int).SetBytes(compactRSV[32:64]) + sig.V = new(big.Int).SetBytes(compactRSV[64:65]) + return &sig, nil +} + // Sign hashes the input then signs it func (k *KeyPair) Sign(message []byte) (ethSig *SignatureData, err error) { msgHash := sha3.NewLegacyKeccak256() From 0fe396789f6a644803aa8bbcbcf1fecf4cf18f7f Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 20 Aug 2024 18:47:51 -0400 Subject: [PATCH 2/8] Include indexed in SolString() Signed-off-by: Peter Broadhurst --- pkg/abi/abi.go | 9 +++++---- pkg/abi/abi_test.go | 7 ++++++- pkg/abi/typecomponents.go | 17 +++++++++++------ pkg/rpcbackend/backend.go | 5 +++-- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index dce418d..a7dd885 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -702,6 +702,7 @@ func (e *Entry) SolidityDef() (string, []string, error) { func (e *Entry) SolidityDefCtx(ctx context.Context) (string, []string, error) { // Everything apart from event and error is a type of function isFunction := e.Type != Error && e.Type != Event + isEvent := e.Type == Event allChildStructs := []string{} buff := new(strings.Builder) @@ -713,7 +714,7 @@ func (e *Entry) SolidityDefCtx(ctx context.Context) (string, []string, error) { if i > 0 { buff.WriteString(", ") } - s, childStructs, err := p.SolidityDefCtx(ctx, isFunction) + s, childStructs, err := p.SolidityDefCtx(ctx, isFunction, isEvent) if err != nil { return "", nil, err } @@ -736,7 +737,7 @@ func (e *Entry) SolidityDefCtx(ctx context.Context) (string, []string, error) { if i > 0 { buff.WriteString(", ") } - s, childStructs, err := p.SolidityDefCtx(ctx, isFunction) + s, childStructs, err := p.SolidityDefCtx(ctx, true, false) if err != nil { return "", nil, err } @@ -782,13 +783,13 @@ func (p *Parameter) SignatureStringCtx(ctx context.Context) (string, error) { return tc.String(), nil } -func (p *Parameter) SolidityDefCtx(ctx context.Context, inFunction bool) (string, []string, error) { +func (p *Parameter) SolidityDefCtx(ctx context.Context, isFunction, isEvent bool) (string, []string, error) { // Ensure the type component tree has been parsed tc, err := p.TypeComponentTreeCtx(ctx) if err != nil { return "", nil, err } - solDef, childStructs := tc.SolidityParamDef(inFunction) + solDef, childStructs := tc.SolidityParamDef(isFunction, isEvent) return solDef, childStructs, nil } diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 853ec61..87ed8b1 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -226,6 +226,11 @@ const sampleABI5 = `[ "internalType": "struct AribtraryWidgets.Widget[]", "name": "widgets", "type": "tuple[]" + }, + { + "name": "account", + "type": "address", + "indexed": true } ], "name": "Invoiced", @@ -1015,7 +1020,7 @@ func TestComplexStructSolidityDef(t *testing.T) { solDef, childStructs, err = abi.Events()["Invoiced"].SolidityDef() assert.NoError(t, err) - assert.Equal(t, "event Invoiced(Customer customer, Widget[] widgets)", solDef) + assert.Equal(t, "event Invoiced(Customer customer, Widget[] widgets, address indexed account)", solDef) assert.Equal(t, []string{ "struct Customer { address owner; bytes32 locator; }", "struct Widget { string description; uint256 price; string[] attributes; }", diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index 00b1abd..cf0681b 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -66,7 +66,7 @@ type TypeComponent interface { DecodeABIData(d []byte, offset int) (*ComponentValue, error) DecodeABIDataCtx(ctx context.Context, d []byte, offest int) (*ComponentValue, error) - SolidityParamDef(inFunction bool) (solDef string, structDefs []string) // gives a string that can be used to define this param in solidity + SolidityParamDef(isFunction, isEvent bool) (solDef string, structDefs []string) // gives a string that can be used to define this param in solidity SolidityTypeDef() (isRef bool, typeDef string, childStructs []string) SolidityStructDef() (structName string, structs []string) } @@ -371,13 +371,18 @@ func (tc *typeComponent) String() string { } } -func (tc *typeComponent) SolidityParamDef(inFunction bool) (string, []string) { +func (tc *typeComponent) SolidityParamDef(isFunction, isEvent bool) (string, []string) { isRef, paramDef, childStructs := tc.SolidityTypeDef() - if isRef && inFunction { - paramDef = fmt.Sprintf("%s memory", paramDef) + if isRef && isFunction { + paramDef += " memory" } - if tc.parameter != nil && tc.parameter.Name != "" { - paramDef = fmt.Sprintf("%s %s", paramDef, tc.parameter.Name) + if tc.parameter != nil { + if isEvent && tc.parameter.Indexed { + paramDef += " indexed" + } + if tc.parameter.Name != "" { + paramDef = fmt.Sprintf("%s %s", paramDef, tc.parameter.Name) + } } return paramDef, childStructs } diff --git a/pkg/rpcbackend/backend.go b/pkg/rpcbackend/backend.go index 36fd3a5..ba9f23e 100644 --- a/pkg/rpcbackend/backend.go +++ b/pkg/rpcbackend/backend.go @@ -19,6 +19,7 @@ package rpcbackend import ( "context" "encoding/json" + "errors" "fmt" "sync/atomic" "time" @@ -91,7 +92,7 @@ type RPCError struct { } func (e *RPCError) Error() error { - return fmt.Errorf(e.Message) + return errors.New(e.Message) } func (e *RPCError) String() string { @@ -208,7 +209,7 @@ func (rc *RPCClient) SyncRequest(ctx context.Context, rpcReq *RPCRequest) (rpcRe rpcMsg = i18n.NewError(ctx, signermsgs.MsgRPCRequestFailed, res.Status()).Error() } log.L(ctx).Errorf("RPC[%s] <-- [%d]: %s", rpcTraceID, res.StatusCode(), errLog) - err := fmt.Errorf(rpcMsg) + err := errors.New(rpcMsg) return rpcRes, err } log.L(ctx).Infof("RPC[%s] <-- %s [%d] OK (%.2fms)", rpcTraceID, rpcReq.Method, res.StatusCode(), float64(time.Since(rpcStartTime))/float64(time.Millisecond)) From a2c5b23d5256c57ff9a49226174590be649049b9 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Fri, 23 Aug 2024 14:40:28 +0100 Subject: [PATCH 3/8] Add unit tests for huge and scientific numbers Signed-off-by: Matthew Whitehead --- pkg/abi/inputparsing.go | 12 +++++++++++- pkg/abi/inputparsing_test.go | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pkg/abi/inputparsing.go b/pkg/abi/inputparsing.go index 54875bd..296386f 100644 --- a/pkg/abi/inputparsing.go +++ b/pkg/abi/inputparsing.go @@ -135,7 +135,17 @@ func getIntegerFromInterface(ctx context.Context, desc string, v interface{}) (* // no prefix means decimal etc. i, ok := i.SetString(vt, 0) if !ok { - return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) + f, _, err := big.ParseFloat(vt, 10, 256, big.ToNearestEven) + if err != nil { + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) + } + i, accuracy := f.Int(i) + if accuracy != big.Exact { + // If we weren't able to decode without losing precision, return an error + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) + } + + return i, nil } return i, nil case *big.Float: diff --git a/pkg/abi/inputparsing_test.go b/pkg/abi/inputparsing_test.go index 0bfb9ad..dcd737d 100644 --- a/pkg/abi/inputparsing_test.go +++ b/pkg/abi/inputparsing_test.go @@ -18,9 +18,11 @@ package abi import ( "context" + "encoding/json" "math/big" "testing" + "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/stretchr/testify/assert" ) @@ -186,6 +188,22 @@ func TestGetIntegerFromInterface(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "-12345", i.String()) + var scientificNumber fftypes.JSONAny + err = json.Unmarshal([]byte("1.0000000000000000000000001e+25"), &scientificNumber) + assert.NoError(t, err) + + i, err = getIntegerFromInterface(ctx, "ut", scientificNumber) + assert.NoError(t, err) + assert.Equal(t, "10000000000000000000000001", i.String()) + + var hugeNumber fftypes.JSONAny + err = json.Unmarshal([]byte("10000000000000000000000000000001"), &hugeNumber) + assert.NoError(t, err) + + i, err = getIntegerFromInterface(ctx, "ut", hugeNumber) + assert.NoError(t, err) + assert.Equal(t, "10000000000000000000000000000001", i.String()) + i32 := int32(-12345) var iPI32 TestInt32PtrCustomType = &i32 i, err = getIntegerFromInterface(ctx, "ut", iPI32) From 3c6f71cfc1081ac2b0ab15fd5b54130e8cdf6f6d Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 27 Aug 2024 14:58:12 +0100 Subject: [PATCH 4/8] Pull in latest ff-common Signed-off-by: Matthew Whitehead --- config.md | 13 +++++++++++++ go.mod | 3 +++ go.sum | 4 ++-- pkg/abi/inputparsing.go | 12 +----------- pkg/abi/inputparsing_test.go | 18 ------------------ 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/config.md b/config.md index aa0c42c..966c9e0 100644 --- a/config.md +++ b/config.md @@ -56,15 +56,25 @@ nav_order: 2 |initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms` |maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s` +## backend.throttle + +|Key|Description|Type|Default Value| +|---|-----------|----|-------------| +|burst|The maximum number of requests that can be made in a short period of time before the throttling kicks in.|`int`|`` +|requestsPerSecond|The average rate at which requests are allowed to pass through over time.|`int`|`` + ## backend.tls |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` @@ -181,10 +191,13 @@ nav_order: 2 |Key|Description|Type|Default Value| |---|-----------|----|-------------| +|ca|The TLS certificate authority in PEM format (this option is ignored if caFile is also set)|`string`|`` |caFile|The path to the CA file for TLS on this API|`string`|`` +|cert|The TLS certificate in PEM format (this option is ignored if certFile is also set)|`string`|`` |certFile|The path to the certificate file for TLS on this API|`string`|`` |clientAuth|Enables or disables client auth for TLS on this API|`string`|`` |enabled|Enables or disables TLS on this API|`boolean`|`false` |insecureSkipHostVerify|When to true in unit test development environments to disable TLS verification. Use with extreme caution|`boolean`|`` +|key|The TLS certificate key in PEM format (this option is ignored if keyFile is also set)|`string`|`` |keyFile|The path to the private key file for TLS on this API|`string`|`` |requiredDNAttributes|A set of required subject DN attributes. Each entry is a regular expression, and the subject certificate must have a matching attribute of the specified type (CN, C, O, OU, ST, L, STREET, POSTALCODE, SERIALNUMBER are valid attributes)|`map[string]string`|`` \ No newline at end of file diff --git a/go.mod b/go.mod index 49f6690..efbb02e 100644 --- a/go.mod +++ b/go.mod @@ -71,8 +71,11 @@ require ( golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/hyperledger/firefly-common => github.com/kaleido-io/firefly-common v0.0.0-20240827134901-edb07289f156 diff --git a/go.sum b/go.sum index 67f32e1..16c8e8e 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,6 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hyperledger/firefly-common v1.4.6 h1:qqXoSaRml3WjUnWcWxrrXs5AIOWa+UcMXLCF8yEa4Pk= -github.com/hyperledger/firefly-common v1.4.6/go.mod h1:jkErZdQmC9fsAJZQO427tURdwB9iiW+NMUZSqS3eBIE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= @@ -56,6 +54,8 @@ github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bww github.com/jarcoal/httpmock v1.2.0/go.mod h1:oCoTsnAz4+UoOUIf5lJOWV2QQIW5UoeUI6aM2YnWAZk= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kaleido-io/firefly-common v0.0.0-20240827134901-edb07289f156 h1:HQpScPoAm9xsACbu9r31wVQ5sQFxLsfe9XzPGY5c4rI= +github.com/kaleido-io/firefly-common v0.0.0-20240827134901-edb07289f156/go.mod h1:dXewcVMFNON2SvQ1UPvu64OWUt77+M3p8qy61lT1kE4= github.com/karlseguin/ccache v2.0.3+incompatible h1:j68C9tWOROiOLWTS/kCGg9IcJG+ACqn5+0+t8Oh83UU= github.com/karlseguin/ccache v2.0.3+incompatible/go.mod h1:CM9tNPzT6EdRh14+jiW8mEF9mkNZuuE51qmgGYUB93w= github.com/karlseguin/expect v1.0.8 h1:Bb0H6IgBWQpadY25UDNkYPDB9ITqK1xnSoZfAq362fw= diff --git a/pkg/abi/inputparsing.go b/pkg/abi/inputparsing.go index 296386f..54875bd 100644 --- a/pkg/abi/inputparsing.go +++ b/pkg/abi/inputparsing.go @@ -135,17 +135,7 @@ func getIntegerFromInterface(ctx context.Context, desc string, v interface{}) (* // no prefix means decimal etc. i, ok := i.SetString(vt, 0) if !ok { - f, _, err := big.ParseFloat(vt, 10, 256, big.ToNearestEven) - if err != nil { - return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) - } - i, accuracy := f.Int(i) - if accuracy != big.Exact { - // If we weren't able to decode without losing precision, return an error - return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) - } - - return i, nil + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) } return i, nil case *big.Float: diff --git a/pkg/abi/inputparsing_test.go b/pkg/abi/inputparsing_test.go index dcd737d..0bfb9ad 100644 --- a/pkg/abi/inputparsing_test.go +++ b/pkg/abi/inputparsing_test.go @@ -18,11 +18,9 @@ package abi import ( "context" - "encoding/json" "math/big" "testing" - "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/stretchr/testify/assert" ) @@ -188,22 +186,6 @@ func TestGetIntegerFromInterface(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "-12345", i.String()) - var scientificNumber fftypes.JSONAny - err = json.Unmarshal([]byte("1.0000000000000000000000001e+25"), &scientificNumber) - assert.NoError(t, err) - - i, err = getIntegerFromInterface(ctx, "ut", scientificNumber) - assert.NoError(t, err) - assert.Equal(t, "10000000000000000000000001", i.String()) - - var hugeNumber fftypes.JSONAny - err = json.Unmarshal([]byte("10000000000000000000000000000001"), &hugeNumber) - assert.NoError(t, err) - - i, err = getIntegerFromInterface(ctx, "ut", hugeNumber) - assert.NoError(t, err) - assert.Equal(t, "10000000000000000000000000000001", i.String()) - i32 := int32(-12345) var iPI32 TestInt32PtrCustomType = &i32 i, err = getIntegerFromInterface(ctx, "ut", iPI32) From 70863b71eb78a67e022900ef4c59a35ef179d967 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 27 Aug 2024 15:56:09 +0100 Subject: [PATCH 5/8] Add unit test for json number Signed-off-by: Matthew Whitehead --- pkg/abi/inputparsing.go | 12 +++++++++++- pkg/abi/inputparsing_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pkg/abi/inputparsing.go b/pkg/abi/inputparsing.go index 54875bd..296386f 100644 --- a/pkg/abi/inputparsing.go +++ b/pkg/abi/inputparsing.go @@ -135,7 +135,17 @@ func getIntegerFromInterface(ctx context.Context, desc string, v interface{}) (* // no prefix means decimal etc. i, ok := i.SetString(vt, 0) if !ok { - return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) + f, _, err := big.ParseFloat(vt, 10, 256, big.ToNearestEven) + if err != nil { + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) + } + i, accuracy := f.Int(i) + if accuracy != big.Exact { + // If we weren't able to decode without losing precision, return an error + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) + } + + return i, nil } return i, nil case *big.Float: diff --git a/pkg/abi/inputparsing_test.go b/pkg/abi/inputparsing_test.go index 0bfb9ad..df79c55 100644 --- a/pkg/abi/inputparsing_test.go +++ b/pkg/abi/inputparsing_test.go @@ -18,9 +18,11 @@ package abi import ( "context" + "encoding/json" "math/big" "testing" + "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/stretchr/testify/assert" ) @@ -186,6 +188,30 @@ func TestGetIntegerFromInterface(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "-12345", i.String()) + var scientificNumber fftypes.JSONAny + err = json.Unmarshal([]byte("1.0000000000000000000000001e+25"), &scientificNumber) + assert.NoError(t, err) + + i, err = getIntegerFromInterface(ctx, "ut", scientificNumber) + assert.NoError(t, err) + assert.Equal(t, "10000000000000000000000001", i.String()) + + var hugeNumber fftypes.JSONAny + err = json.Unmarshal([]byte("10000000000000000000000000000001"), &hugeNumber) + assert.NoError(t, err) + + i, err = getIntegerFromInterface(ctx, "ut", hugeNumber) + assert.NoError(t, err) + assert.Equal(t, "10000000000000000000000000000001", i.String()) + + var jsonNumber json.Number + err = json.Unmarshal([]byte("20000000000000000000000000000002"), &jsonNumber) + assert.NoError(t, err) + + i, err = getIntegerFromInterface(ctx, "ut", jsonNumber) + assert.NoError(t, err) + assert.Equal(t, "20000000000000000000000000000002", i.String()) + i32 := int32(-12345) var iPI32 TestInt32PtrCustomType = &i32 i, err = getIntegerFromInterface(ctx, "ut", iPI32) From a3cfbb735bb3d0a625c8b3337f96f574a6a21fea Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 27 Aug 2024 13:47:39 -0400 Subject: [PATCH 6/8] Increase handling of JSON Number large numbers (vs. string) Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 3 ++ pkg/abi/inputparsing.go | 65 +++++++++-------------- pkg/ethtypes/hexinteger.go | 29 ++++------- pkg/ethtypes/hexinteger_test.go | 5 +- pkg/ethtypes/hexuint64.go | 25 ++++----- pkg/ethtypes/hexuint64_test.go | 18 ++++++- pkg/ethtypes/integer_parsing.go | 68 +++++++++++++++++++++++++ pkg/ethtypes/integer_parsing_test.go | 46 +++++++++++++++++ 8 files changed, 179 insertions(+), 80 deletions(-) create mode 100644 pkg/ethtypes/integer_parsing.go create mode 100644 pkg/ethtypes/integer_parsing_test.go diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index 95d832c..df5751b 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -104,4 +104,7 @@ var ( MsgInvalidEIP155TransactionV = ffe("FF22085", "Invalid V value from EIP-155 transaction (chainId=%d)") MsgInvalidChainID = ffe("FF22086", "Invalid chainId expected=%d actual=%d") MsgSigningInvalidCompactRSV = ffe("FF22087", "Invalid signature data (compact R,S,V) length=%d (expected=65)") + MsgInvalidNumberString = ffe("FF22088", "Invalid integer string '%s'") + MsgInvalidIntPrecisionLoss = ffe("FF22089", "String %s cannot be converted to integer without losing precision") + MsgInvalidUint64PrecisionLoss = ffe("FF22090", "String %s cannot be converted to a uint64 without losing precision") ) diff --git a/pkg/abi/inputparsing.go b/pkg/abi/inputparsing.go index 296386f..c347b29 100644 --- a/pkg/abi/inputparsing.go +++ b/pkg/abi/inputparsing.go @@ -19,6 +19,7 @@ package abi import ( "context" "encoding/hex" + "encoding/json" "fmt" "math/big" "reflect" @@ -26,6 +27,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-signer/internal/signermsgs" + "github.com/hyperledger/firefly-signer/pkg/ethtypes" ) var ( @@ -128,68 +130,49 @@ func getFloat64IfConvertible(v interface{}) (float64, bool) { // with a focus on those generated by the result of an Unmarshal using Go's default // unmarshalling. func getIntegerFromInterface(ctx context.Context, desc string, v interface{}) (*big.Int, error) { - i := new(big.Int) switch vt := v.(type) { + case json.Number: + i, err := ethtypes.BigIntegerFromString(ctx, vt.String()) + if err != nil { + return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) + } + return i, nil case string: - // We use Go's default '0' base integer parsing, where `0x` means hex, - // no prefix means decimal etc. - i, ok := i.SetString(vt, 0) - if !ok { - f, _, err := big.ParseFloat(vt, 10, 256, big.ToNearestEven) - if err != nil { - return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) - } - i, accuracy := f.Int(i) - if accuracy != big.Exact { - // If we weren't able to decode without losing precision, return an error - return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) - } - - return i, nil + i, err := ethtypes.BigIntegerFromString(ctx, vt) + if err != nil { + return nil, i18n.WrapError(ctx, err, signermsgs.MsgInvalidIntegerABIInput, vt, v, desc) } return i, nil case *big.Float: - i, _ := vt.Int(i) + i, _ := vt.Int(nil) return i, nil case *big.Int: return vt, nil case float64: // This is how JSON numbers come in (no distinction between integers/floats) - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case float32: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case int64: - i.SetInt64(vt) - return i, nil + return new(big.Int).SetInt64(vt), nil case int32: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case int16: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case int8: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case int: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case uint64: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetUint64(vt), nil case uint32: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case uint16: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case uint8: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetInt64(int64(vt)), nil case uint: - i.SetInt64(int64(vt)) - return i, nil + return new(big.Int).SetUint64(uint64(vt)), nil default: if str, ok := getStringIfConvertible(v); ok { return getIntegerFromInterface(ctx, desc, str) diff --git a/pkg/ethtypes/hexinteger.go b/pkg/ethtypes/hexinteger.go index 5672d5c..d18a1e9 100644 --- a/pkg/ethtypes/hexinteger.go +++ b/pkg/ethtypes/hexinteger.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -18,7 +18,6 @@ package ethtypes import ( "context" - "encoding/json" "fmt" "math/big" @@ -37,25 +36,15 @@ func (h HexInteger) MarshalJSON() ([]byte, error) { } func (h *HexInteger) UnmarshalJSON(b []byte) error { - var i interface{} - _ = json.Unmarshal(b, &i) - switch i := i.(type) { - case float64: - *h = HexInteger(*big.NewInt(int64(i))) - return nil - case string: - bi, ok := new(big.Int).SetString(i, 0) - if !ok { - return fmt.Errorf("unable to parse integer: %s", i) - } - if bi.Sign() < 0 { - return fmt.Errorf("negative values are not supported: %s", i) - } - *h = HexInteger(*bi) - return nil - default: - return fmt.Errorf("unable to parse integer from type %T", i) + bi, err := UnmarshalBigInt(b) + if err != nil { + return err + } + if bi.Sign() < 0 { + return fmt.Errorf("negative values are not supported: %s", b) } + *h = HexInteger(*bi) + return nil } func (h *HexInteger) BigInt() *big.Int { diff --git a/pkg/ethtypes/hexinteger_test.go b/pkg/ethtypes/hexinteger_test.go index 097f976..f473381 100644 --- a/pkg/ethtypes/hexinteger_test.go +++ b/pkg/ethtypes/hexinteger_test.go @@ -73,7 +73,10 @@ func TestHexIntegerMissingBytes(t *testing.T) { }` err := json.Unmarshal([]byte(testData), &testStruct) - assert.Regexp(t, "unable to parse integer", err) + assert.Regexp(t, "FF22088", err) + + err = testStruct.I1.UnmarshalJSON([]byte(`{!badJSON`)) + assert.Regexp(t, "invalid", err) } func TestHexIntegerBadType(t *testing.T) { diff --git a/pkg/ethtypes/hexuint64.go b/pkg/ethtypes/hexuint64.go index 4c7f757..bbfb02a 100644 --- a/pkg/ethtypes/hexuint64.go +++ b/pkg/ethtypes/hexuint64.go @@ -18,11 +18,11 @@ package ethtypes import ( "context" - "encoding/json" "fmt" "strconv" "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-signer/internal/signermsgs" ) // HexUint64 is a positive integer - serializes to JSON as an 0x hex string (no leading zeros), and parses flexibly depending on the prefix (so 0x for hex, or base 10 for plain string / float64) @@ -40,22 +40,15 @@ func (h HexUint64) MarshalJSON() ([]byte, error) { } func (h *HexUint64) UnmarshalJSON(b []byte) error { - var i interface{} - _ = json.Unmarshal(b, &i) - switch i := i.(type) { - case float64: - *h = HexUint64(i) - return nil - case string: - i64, err := strconv.ParseUint(i, 0, 64) - if err != nil { - return fmt.Errorf("unable to parse integer: %s", i) - } - *h = HexUint64(i64) - return nil - default: - return fmt.Errorf("unable to parse integer from type %T", i) + bi, err := UnmarshalBigInt(b) + if err != nil { + return err + } + if !bi.IsUint64() { + return i18n.NewError(context.Background(), signermsgs.MsgInvalidUint64PrecisionLoss, b) } + *h = HexUint64(bi.Uint64()) + return nil } func (h HexUint64) Uint64() uint64 { diff --git a/pkg/ethtypes/hexuint64_test.go b/pkg/ethtypes/hexuint64_test.go index 3acdd78..17dad14 100644 --- a/pkg/ethtypes/hexuint64_test.go +++ b/pkg/ethtypes/hexuint64_test.go @@ -73,7 +73,7 @@ func TestHexUint64MissingBytes(t *testing.T) { }` err := json.Unmarshal([]byte(testData), &testStruct) - assert.Regexp(t, "unable to parse integer", err) + assert.Regexp(t, "FF22088", err) } func TestHexUint64BadType(t *testing.T) { @@ -115,7 +115,21 @@ func TestHexUint64BadNegative(t *testing.T) { }` err := json.Unmarshal([]byte(testData), &testStruct) - assert.Regexp(t, "parse", err) + assert.Regexp(t, "FF22090", err) +} + +func TestHexUint64BadTooLarge(t *testing.T) { + + testStruct := struct { + I1 HexUint64 `json:"i1"` + }{} + + testData := `{ + "i1": "18446744073709551616" + }` + + err := json.Unmarshal([]byte(testData), &testStruct) + assert.Regexp(t, "FF22090", err) } func TestHexUint64Constructor(t *testing.T) { diff --git a/pkg/ethtypes/integer_parsing.go b/pkg/ethtypes/integer_parsing.go new file mode 100644 index 0000000..ed1e844 --- /dev/null +++ b/pkg/ethtypes/integer_parsing.go @@ -0,0 +1,68 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ethtypes + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "math/big" + + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-common/pkg/log" + "github.com/hyperledger/firefly-signer/internal/signermsgs" +) + +func BigIntegerFromString(ctx context.Context, s string) (*big.Int, error) { + // We use Go's default '0' base integer parsing, where `0x` means hex, + // no prefix means decimal etc. + i, ok := new(big.Int).SetString(s, 0) + if !ok { + f, _, err := big.ParseFloat(s, 10, 256, big.ToNearestEven) + if err != nil { + log.L(ctx).Errorf("Error parsing numeric string '%s': %s", s, err) + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidNumberString, s) + } + i, accuracy := f.Int(i) + if accuracy != big.Exact { + // If we weren't able to decode without losing precision, return an error + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidIntPrecisionLoss, s) + } + + return i, nil + } + return i, nil +} + +func UnmarshalBigInt(b []byte) (*big.Int, error) { + var i interface{} + d := json.NewDecoder(bytes.NewReader(b)) + d.UseNumber() + err := d.Decode(&i) + if err != nil { + return nil, err + } + switch i := i.(type) { + case json.Number: + return BigIntegerFromString(context.Background(), i.String()) + case string: + return BigIntegerFromString(context.Background(), i) + default: + return nil, fmt.Errorf("unable to parse integer from type %T", i) + } +} diff --git a/pkg/ethtypes/integer_parsing_test.go b/pkg/ethtypes/integer_parsing_test.go new file mode 100644 index 0000000..b5e4c06 --- /dev/null +++ b/pkg/ethtypes/integer_parsing_test.go @@ -0,0 +1,46 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ethtypes + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIntegerParsing(t *testing.T) { + ctx := context.Background() + + i, err := BigIntegerFromString(ctx, "1.0000000000000000000000001e+25") + assert.NoError(t, err) + assert.Equal(t, "10000000000000000000000001", i.String()) + + i, err = BigIntegerFromString(ctx, "10000000000000000000000000000001") + assert.NoError(t, err) + assert.Equal(t, "10000000000000000000000000000001", i.String()) + + i, err = BigIntegerFromString(ctx, "20000000000000000000000000000002") + assert.NoError(t, err) + assert.Equal(t, "20000000000000000000000000000002", i.String()) + + _, err = BigIntegerFromString(ctx, "0xGG") + assert.Regexp(t, "FF22088", err) + + _, err = BigIntegerFromString(ctx, "3.0000000000000000000000000000003") + assert.Regexp(t, "FF22089", err) +} From d2fadf02a249414c11d5e9d3a63ec4fc4c4e26f7 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 27 Aug 2024 14:14:43 -0400 Subject: [PATCH 7/8] Bools to enum Signed-off-by: Peter Broadhurst --- pkg/abi/abi.go | 19 ++++++++++++------- pkg/abi/typecomponents.go | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index a7dd885..ef0466a 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -701,8 +701,13 @@ func (e *Entry) SolidityDef() (string, []string, error) { // SolidityDefCtx returns a Solidity-like descriptor of the entry, including its type func (e *Entry) SolidityDefCtx(ctx context.Context) (string, []string, error) { // Everything apart from event and error is a type of function - isFunction := e.Type != Error && e.Type != Event - isEvent := e.Type == Event + var fieldType SolFieldType + switch e.Type { + case Error, Event: + fieldType = EventOrErrorField + default: + fieldType = FunctionInput + } allChildStructs := []string{} buff := new(strings.Builder) @@ -714,7 +719,7 @@ func (e *Entry) SolidityDefCtx(ctx context.Context) (string, []string, error) { if i > 0 { buff.WriteString(", ") } - s, childStructs, err := p.SolidityDefCtx(ctx, isFunction, isEvent) + s, childStructs, err := p.SolidityDefCtx(ctx, fieldType) if err != nil { return "", nil, err } @@ -723,7 +728,7 @@ func (e *Entry) SolidityDefCtx(ctx context.Context) (string, []string, error) { } buff.WriteRune(')') - if isFunction { + if fieldType == FunctionInput { buff.WriteString(" external") if e.StateMutability != "" && // The state mutability nonpayable is reflected in Solidity by not specifying a state mutability modifier at all. @@ -737,7 +742,7 @@ func (e *Entry) SolidityDefCtx(ctx context.Context) (string, []string, error) { if i > 0 { buff.WriteString(", ") } - s, childStructs, err := p.SolidityDefCtx(ctx, true, false) + s, childStructs, err := p.SolidityDefCtx(ctx, fieldType) if err != nil { return "", nil, err } @@ -783,13 +788,13 @@ func (p *Parameter) SignatureStringCtx(ctx context.Context) (string, error) { return tc.String(), nil } -func (p *Parameter) SolidityDefCtx(ctx context.Context, isFunction, isEvent bool) (string, []string, error) { +func (p *Parameter) SolidityDefCtx(ctx context.Context, fieldType SolFieldType) (string, []string, error) { // Ensure the type component tree has been parsed tc, err := p.TypeComponentTreeCtx(ctx) if err != nil { return "", nil, err } - solDef, childStructs := tc.SolidityParamDef(isFunction, isEvent) + solDef, childStructs := tc.SolidityParamDef(fieldType) return solDef, childStructs, nil } diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index cf0681b..91ebda6 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -66,7 +66,7 @@ type TypeComponent interface { DecodeABIData(d []byte, offset int) (*ComponentValue, error) DecodeABIDataCtx(ctx context.Context, d []byte, offest int) (*ComponentValue, error) - SolidityParamDef(isFunction, isEvent bool) (solDef string, structDefs []string) // gives a string that can be used to define this param in solidity + SolidityParamDef(fieldType SolFieldType) (solDef string, structDefs []string) // gives a string that can be used to define this param in solidity SolidityTypeDef() (isRef bool, typeDef string, childStructs []string) SolidityStructDef() (structName string, structs []string) } @@ -187,6 +187,14 @@ const ( BaseTypeString BaseTypeName = "string" ) +type SolFieldType int + +const ( + FunctionInput SolFieldType = iota // input to a function, or a constructor + EventOrErrorField // a field of an event or an error + StructField // a field of a struct +) + // tupleTypeString appears in the same place in the ABI as elementary type strings, but it is not an elementary type. // We treat it separately. const tupleTypeString = "tuple" @@ -371,13 +379,13 @@ func (tc *typeComponent) String() string { } } -func (tc *typeComponent) SolidityParamDef(isFunction, isEvent bool) (string, []string) { +func (tc *typeComponent) SolidityParamDef(fieldType SolFieldType) (string, []string) { isRef, paramDef, childStructs := tc.SolidityTypeDef() - if isRef && isFunction { + if isRef && fieldType == FunctionInput { paramDef += " memory" } if tc.parameter != nil { - if isEvent && tc.parameter.Indexed { + if fieldType == EventOrErrorField && tc.parameter.Indexed { paramDef += " indexed" } if tc.parameter.Name != "" { From 2fe278d0353fcd5c792c4fccdbcfc8fa34d4caab Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 27 Aug 2024 14:52:35 -0400 Subject: [PATCH 8/8] Better errors Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 1 + pkg/ethtypes/hexinteger.go | 2 +- pkg/ethtypes/hexinteger_test.go | 2 +- pkg/ethtypes/hexuint64.go | 2 +- pkg/ethtypes/hexuint64_test.go | 2 +- pkg/ethtypes/integer_parsing.go | 5 ++--- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index df5751b..0e5a700 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -107,4 +107,5 @@ var ( MsgInvalidNumberString = ffe("FF22088", "Invalid integer string '%s'") MsgInvalidIntPrecisionLoss = ffe("FF22089", "String %s cannot be converted to integer without losing precision") MsgInvalidUint64PrecisionLoss = ffe("FF22090", "String %s cannot be converted to a uint64 without losing precision") + MsgInvalidJSONTypeForBigInt = ffe("FF22091", "JSON parsed '%T' cannot be converted to an integer") ) diff --git a/pkg/ethtypes/hexinteger.go b/pkg/ethtypes/hexinteger.go index d18a1e9..c734c47 100644 --- a/pkg/ethtypes/hexinteger.go +++ b/pkg/ethtypes/hexinteger.go @@ -36,7 +36,7 @@ func (h HexInteger) MarshalJSON() ([]byte, error) { } func (h *HexInteger) UnmarshalJSON(b []byte) error { - bi, err := UnmarshalBigInt(b) + bi, err := UnmarshalBigInt(context.Background(), b) if err != nil { return err } diff --git a/pkg/ethtypes/hexinteger_test.go b/pkg/ethtypes/hexinteger_test.go index f473381..8a3d611 100644 --- a/pkg/ethtypes/hexinteger_test.go +++ b/pkg/ethtypes/hexinteger_test.go @@ -90,7 +90,7 @@ func TestHexIntegerBadType(t *testing.T) { }` err := json.Unmarshal([]byte(testData), &testStruct) - assert.Regexp(t, "unable to parse integer", err) + assert.Regexp(t, "FF22091", err) } func TestHexIntegerBadJSON(t *testing.T) { diff --git a/pkg/ethtypes/hexuint64.go b/pkg/ethtypes/hexuint64.go index bbfb02a..a9356fb 100644 --- a/pkg/ethtypes/hexuint64.go +++ b/pkg/ethtypes/hexuint64.go @@ -40,7 +40,7 @@ func (h HexUint64) MarshalJSON() ([]byte, error) { } func (h *HexUint64) UnmarshalJSON(b []byte) error { - bi, err := UnmarshalBigInt(b) + bi, err := UnmarshalBigInt(context.Background(), b) if err != nil { return err } diff --git a/pkg/ethtypes/hexuint64_test.go b/pkg/ethtypes/hexuint64_test.go index 17dad14..7f246f8 100644 --- a/pkg/ethtypes/hexuint64_test.go +++ b/pkg/ethtypes/hexuint64_test.go @@ -87,7 +87,7 @@ func TestHexUint64BadType(t *testing.T) { }` err := json.Unmarshal([]byte(testData), &testStruct) - assert.Regexp(t, "unable to parse integer", err) + assert.Regexp(t, "FF22091", err) } func TestHexUint64BadJSON(t *testing.T) { diff --git a/pkg/ethtypes/integer_parsing.go b/pkg/ethtypes/integer_parsing.go index ed1e844..f2f5427 100644 --- a/pkg/ethtypes/integer_parsing.go +++ b/pkg/ethtypes/integer_parsing.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "encoding/json" - "fmt" "math/big" "github.com/hyperledger/firefly-common/pkg/i18n" @@ -49,7 +48,7 @@ func BigIntegerFromString(ctx context.Context, s string) (*big.Int, error) { return i, nil } -func UnmarshalBigInt(b []byte) (*big.Int, error) { +func UnmarshalBigInt(ctx context.Context, b []byte) (*big.Int, error) { var i interface{} d := json.NewDecoder(bytes.NewReader(b)) d.UseNumber() @@ -63,6 +62,6 @@ func UnmarshalBigInt(b []byte) (*big.Int, error) { case string: return BigIntegerFromString(context.Background(), i) default: - return nil, fmt.Errorf("unable to parse integer from type %T", i) + return nil, i18n.NewError(ctx, signermsgs.MsgInvalidJSONTypeForBigInt, i) } }