From 661aadf19523f2a628c47574509778fbe0075d48 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 17 Sep 2024 11:21:15 +0100 Subject: [PATCH] Handle large numbers when unmarshalling to an operation Signed-off-by: Matthew Whitehead --- internal/contracts/operations.go | 5 ++- internal/contracts/operations_test.go | 58 +++++++++++++++++++++++++++ internal/txcommon/contract_inputs.go | 5 ++- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/internal/contracts/operations.go b/internal/contracts/operations.go index c308aa51a..408f29adb 100644 --- a/internal/contracts/operations.go +++ b/internal/contracts/operations.go @@ -17,6 +17,7 @@ package contracts import ( + "bytes" "context" "encoding/json" @@ -38,7 +39,9 @@ type blockchainContractDeployData struct { func addBlockchainReqInputs(op *core.Operation, req interface{}) (err error) { var reqJSON []byte if reqJSON, err = json.Marshal(req); err == nil { - err = json.Unmarshal(reqJSON, &op.Input) + d := json.NewDecoder(bytes.NewReader(reqJSON)) + d.UseNumber() + err = d.Decode(&op.Input) } return err } diff --git a/internal/contracts/operations_test.go b/internal/contracts/operations_test.go index bd425dabc..fe30874fc 100644 --- a/internal/contracts/operations_test.go +++ b/internal/contracts/operations_test.go @@ -16,7 +16,9 @@ package contracts import ( + "bytes" "context" + "encoding/json" "fmt" "testing" @@ -35,6 +37,21 @@ import ( "github.com/stretchr/testify/mock" ) +const sampleRequestLargeNumberInput = `{ + "location": { + "address": "0x1111" + }, + "key": "0x123", + "method": { + "name": "set", + "params": null, + "returns": null + }, + "input": { + "value": 10000000000000000000000000001 + } +}` + func reqWithMessage(msgType core.MessageType) *core.ContractCallRequest { return &core.ContractCallRequest{ Key: "0x123", @@ -60,6 +77,7 @@ func reqWithMessage(msgType core.MessageType) *core.ContractCallRequest { } func TestPrepareAndRunBlockchainInvoke(t *testing.T) { + cm := newTestContractManager() op := &core.Operation{ @@ -101,6 +119,46 @@ func TestPrepareAndRunBlockchainInvoke(t *testing.T) { mbi.AssertExpectations(t) } +func TestPrepareAndRunBlockchainInvokeLargeNumberInput(t *testing.T) { + + cm := newTestContractManager() + + var req core.ContractCallRequest + d := json.NewDecoder(bytes.NewReader([]byte(sampleRequestLargeNumberInput))) + d.UseNumber() + err := d.Decode(&req) + assert.NoError(t, err) + + op := &core.Operation{ + Type: core.OpTypeBlockchainInvoke, + ID: fftypes.NewUUID(), + Namespace: "ns1", + } + + err = addBlockchainReqInputs(op, req) + assert.NoError(t, err) + + mbi := cm.blockchain.(*blockchainmocks.Plugin) + opaqueData := "anything" + mbi.On("ParseInterface", context.Background(), mock.MatchedBy(func(method *fftypes.FFIMethod) bool { + return method.Name == req.Method.Name + }), req.Errors).Return(opaqueData, nil) + mbi.On("InvokeContract", context.Background(), "ns1:"+op.ID.String(), "0x123", mock.MatchedBy(func(loc *fftypes.JSONAny) bool { + return loc.String() == req.Location.String() + }), opaqueData, req.Input, req.Options, (*blockchain.BatchPin)(nil)).Return(false, nil) + + po, err := cm.PrepareOperation(context.Background(), op) + assert.NoError(t, err) + assert.Equal(t, &req, po.Data.(txcommon.BlockchainInvokeData).Request) + + _, phase, err := cm.RunOperation(context.Background(), po) + + assert.Equal(t, core.OpPhasePending, phase) + assert.NoError(t, err) + + mbi.AssertExpectations(t) +} + func TestPrepareAndRunBlockchainInvokeRejected(t *testing.T) { cm := newTestContractManager() diff --git a/internal/txcommon/contract_inputs.go b/internal/txcommon/contract_inputs.go index 942ca6625..f2fead6ce 100644 --- a/internal/txcommon/contract_inputs.go +++ b/internal/txcommon/contract_inputs.go @@ -17,6 +17,7 @@ package txcommon import ( + "bytes" "context" "encoding/json" @@ -39,7 +40,9 @@ type BlockchainInvokeData struct { func RetrieveBlockchainInvokeInputs(ctx context.Context, op *core.Operation) (*core.ContractCallRequest, error) { var req core.ContractCallRequest s := op.Input.String() - if err := json.Unmarshal([]byte(s), &req); err != nil { + d := json.NewDecoder(bytes.NewReader([]byte(s))) + d.UseNumber() + if err := d.Decode(&req); err != nil { return nil, i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, s) } return &req, nil