From 91277cdf91b5b8145fc12e8648b253c698508486 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 19 Mar 2024 01:10:10 +0500 Subject: [PATCH 1/5] Add support deploy contract for Tezos Connector Signed-off-by: Alexey Semenyuk --- internal/tezos/deploy_contract_prepare.go | 55 +++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/internal/tezos/deploy_contract_prepare.go b/internal/tezos/deploy_contract_prepare.go index d29e28f..bf58137 100644 --- a/internal/tezos/deploy_contract_prepare.go +++ b/internal/tezos/deploy_contract_prepare.go @@ -2,11 +2,60 @@ package tezos import ( "context" - "errors" + "encoding/hex" + "encoding/json" + "blockwatch.cc/tzgo/codec" + "blockwatch.cc/tzgo/micheline" + "blockwatch.cc/tzgo/rpc" + "blockwatch.cc/tzgo/tezos" + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-common/pkg/log" + "github.com/hyperledger/firefly-tezosconnect/internal/msgs" "github.com/hyperledger/firefly-transaction-manager/pkg/ffcapi" ) -func (c *tezosConnector) DeployContractPrepare(_ context.Context, _ *ffcapi.ContractDeployPrepareRequest) (*ffcapi.TransactionPrepareResponse, ffcapi.ErrorReason, error) { - return nil, "", errors.New("contract deployment is not supported") +func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi.ContractDeployPrepareRequest) (*ffcapi.TransactionPrepareResponse, ffcapi.ErrorReason, error) { + sc, err := asScript(req.Contract.String()) + if err != nil { + return nil, ffcapi.ErrorReasonInvalidInputs, err + } + orig := &codec.Origination{ + Script: sc, + } + + addr, err := tezos.ParseAddress(req.From) + if err != nil { + return nil, ffcapi.ErrorReasonInvalidInputs, i18n.NewError(ctx, msgs.MsgInvalidFromAddress, req.From, err) + } + + hash, _ := c.client.GetBlockHash(ctx, rpc.Head) + op := codec.NewOp(). + WithContents(orig). + WithSource(addr). + WithBranch(hash) + + err = c.completeOp(ctx, op, req.From, req.Nonce) + if err != nil { + return nil, ffcapi.ErrorReasonInvalidInputs, err + } + opts := &rpc.DefaultOptions + if reason, err := c.estimateAndAssignTxCost(ctx, op, opts); err != nil { + return nil, reason, err + } + + log.L(ctx).Infof("Prepared deploy transaction dataLen=%d gas=%s", len(op.Bytes()), req.Gas.Int()) + + return &ffcapi.TransactionPrepareResponse{ + Gas: req.Gas, + TransactionData: hex.EncodeToString(op.Bytes()), + }, "", nil +} + +func asScript(s string) (micheline.Script, error) { + var sc micheline.Script + if err := json.Unmarshal([]byte(s), &sc); err != nil { + return micheline.Script{}, err + } + return sc, nil } From c4098c7ff7be7cd335346cc281ab55daf7462f4b Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 19 Mar 2024 13:07:09 +0500 Subject: [PATCH 2/5] Test Signed-off-by: Alexey Semenyuk --- internal/tezos/deploy_contract_prepare.go | 8 +- .../tezos/deploy_contract_prepare_test.go | 125 +++++++++++++++++- 2 files changed, 126 insertions(+), 7 deletions(-) diff --git a/internal/tezos/deploy_contract_prepare.go b/internal/tezos/deploy_contract_prepare.go index bf58137..857447c 100644 --- a/internal/tezos/deploy_contract_prepare.go +++ b/internal/tezos/deploy_contract_prepare.go @@ -16,6 +16,10 @@ import ( ) func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi.ContractDeployPrepareRequest) (*ffcapi.TransactionPrepareResponse, ffcapi.ErrorReason, error) { + if req.Contract == nil { + return nil, ffcapi.ErrorReasonInvalidInputs, i18n.NewError(ctx, "Missing contract", req.Contract) + } + sc, err := asScript(req.Contract.String()) if err != nil { return nil, ffcapi.ErrorReasonInvalidInputs, err @@ -54,8 +58,6 @@ func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi. func asScript(s string) (micheline.Script, error) { var sc micheline.Script - if err := json.Unmarshal([]byte(s), &sc); err != nil { - return micheline.Script{}, err - } + _ = json.Unmarshal([]byte(s), &sc) return sc, nil } diff --git a/internal/tezos/deploy_contract_prepare_test.go b/internal/tezos/deploy_contract_prepare_test.go index ccf3bad..a238860 100644 --- a/internal/tezos/deploy_contract_prepare_test.go +++ b/internal/tezos/deploy_contract_prepare_test.go @@ -1,18 +1,135 @@ package tezos import ( - "context" + "blockwatch.cc/tzgo/rpc" + "blockwatch.cc/tzgo/tezos" + "github.com/stretchr/testify/mock" "testing" + "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-transaction-manager/pkg/ffcapi" "github.com/stretchr/testify/assert" ) func TestDeployContractPrepare(t *testing.T) { - _, c, _, done := newTestConnector(t) + ctx, c, mRPC, done := newTestConnector(t) defer done() + mRPC.On("GetBlockHash", ctx, mock.Anything, mock.Anything). + Return(tezos.BlockHash{}, nil) + mRPC.On("GetContractExt", ctx, mock.Anything, mock.Anything). + Return(&rpc.ContractInfo{ + Counter: 10, + Manager: "edpkv89Jj4aVWetK69CWm5ss1LayvK8dQoiFz7p995y1k3E8CZwqJ6", + }, nil) + mRPC.On("Simulate", ctx, mock.Anything, mock.Anything). + Return(&rpc.Receipt{ + Op: &rpc.Operation{ + Contents: []rpc.TypedOperation{ + rpc.Transaction{ + Manager: rpc.Manager{ + Generic: rpc.Generic{ + Metadata: rpc.OperationMetadata{ + Result: rpc.OperationResult{ + Status: tezos.OpStatusApplied, + }, + }, + }, + }, + }, + }, + }, + }, nil) - _, _, err := c.DeployContractPrepare(context.Background(), &ffcapi.ContractDeployPrepareRequest{}) + resp, reason, err := c.DeployContractPrepare(ctx, &ffcapi.ContractDeployPrepareRequest{ + Contract: fftypes.JSONAnyPtr("{\"code\":[{\"args\":[{\"prim\":\"string\"}],\"prim\":\"parameter\"},{\"args\":[{\"prim\":\"string\"}],\"prim\":\"storage\"},{\"args\":[[{\"prim\":\"CAR\"},{\"args\":[{\"prim\":\"operation\"}],\"prim\":\"NIL\"},{\"prim\":\"PAIR\"}]],\"prim\":\"code\"}],\"storage\":{\"string\":\"hello\"}}"), + }) + + assert.NotNil(t, resp) + assert.Equal(t, reason, ffcapi.ErrorReason("")) + assert.NoError(t, err) +} + +func TestDeployContractPrepareGetContractExtError(t *testing.T) { + ctx, c, mRPC, done := newTestConnector(t) + defer done() + mRPC.On("GetBlockHash", ctx, mock.Anything, mock.Anything). + Return(tezos.BlockHash{}, nil) + mRPC.On("GetContractExt", ctx, mock.Anything, mock.Anything). + Return(&rpc.ContractInfo{ + Counter: 10, + Manager: "edpkv89Jj4aVWetK69CWm5ss1LayvK8dQoiFz7p995y1k3E8CZwqJ6", + }, assert.AnError) + + resp, reason, err := c.DeployContractPrepare(ctx, &ffcapi.ContractDeployPrepareRequest{ + Contract: fftypes.JSONAnyPtr("{\"code\":[{\"args\":[{\"prim\":\"string\"}],\"prim\":\"parameter\"},{\"args\":[{\"prim\":\"string\"}],\"prim\":\"storage\"},{\"args\":[[{\"prim\":\"CAR\"},{\"args\":[{\"prim\":\"operation\"}],\"prim\":\"NIL\"},{\"prim\":\"PAIR\"}]],\"prim\":\"code\"}],\"storage\":{\"string\":\"hello\"}}"), + }) + + assert.Nil(t, resp) + assert.Error(t, err) + assert.Equal(t, reason, ffcapi.ErrorReasonInvalidInputs) +} + +func TestDeployContractPrepareSimulateError(t *testing.T) { + ctx, c, mRPC, done := newTestConnector(t) + defer done() + mRPC.On("GetBlockHash", ctx, mock.Anything, mock.Anything). + Return(tezos.BlockHash{}, nil) + mRPC.On("GetContractExt", ctx, mock.Anything, mock.Anything). + Return(&rpc.ContractInfo{ + Counter: 10, + Manager: "edpkv89Jj4aVWetK69CWm5ss1LayvK8dQoiFz7p995y1k3E8CZwqJ6", + }, nil) + mRPC.On("Simulate", ctx, mock.Anything, mock.Anything). + Return(&rpc.Receipt{ + Op: &rpc.Operation{ + Contents: []rpc.TypedOperation{ + rpc.Transaction{ + Manager: rpc.Manager{ + Generic: rpc.Generic{ + Metadata: rpc.OperationMetadata{ + Result: rpc.OperationResult{ + Status: tezos.OpStatusApplied, + }, + }, + }, + }, + }, + }, + }, + }, assert.AnError) + + resp, reason, err := c.DeployContractPrepare(ctx, &ffcapi.ContractDeployPrepareRequest{ + Contract: fftypes.JSONAnyPtr("{\"code\":[{\"args\":[{\"prim\":\"string\"}],\"prim\":\"parameter\"},{\"args\":[{\"prim\":\"string\"}],\"prim\":\"storage\"},{\"args\":[[{\"prim\":\"CAR\"},{\"args\":[{\"prim\":\"operation\"}],\"prim\":\"NIL\"},{\"prim\":\"PAIR\"}]],\"prim\":\"code\"}],\"storage\":{\"string\":\"hello\"}}"), + }) + + assert.Nil(t, resp) + assert.Error(t, err) + assert.Equal(t, reason, ffcapi.ErrorReason("")) +} + +func TestDeployContractPrepareMisingContractError(t *testing.T) { + ctx, c, _, done := newTestConnector(t) + defer done() + + resp, reason, err := c.DeployContractPrepare(ctx, &ffcapi.ContractDeployPrepareRequest{}) + + assert.Nil(t, resp) + assert.Error(t, err) + assert.Equal(t, reason, ffcapi.ErrorReasonInvalidInputs) +} + +func TestDeployContractPrepareParseAddressError(t *testing.T) { + ctx, c, _, done := newTestConnector(t) + defer done() + + resp, reason, err := c.DeployContractPrepare(ctx, &ffcapi.ContractDeployPrepareRequest{ + TransactionHeaders: ffcapi.TransactionHeaders{ + From: "wrong", + }, + Contract: fftypes.JSONAnyPtr("{\"code\":[{\"args\":[{\"prim\":\"string\"}],\"prim\":\"parameter\"},{\"args\":[{\"prim\":\"string\"}],\"prim\":\"storage\"},{\"args\":[[{\"prim\":\"CAR\"},{\"args\":[{\"prim\":\"operation\"}],\"prim\":\"NIL\"},{\"prim\":\"PAIR\"}]],\"prim\":\"code\"}],\"storage\":{\"string\":\"hello\"}}"), + }) + + assert.Nil(t, resp) assert.Error(t, err) - assert.Equal(t, err.Error(), "contract deployment is not supported") + assert.Equal(t, reason, ffcapi.ErrorReasonInvalidInputs) } From 141de006691a5a6cef4c453713da54bbeccec370 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Wed, 20 Mar 2024 01:24:20 +0500 Subject: [PATCH 3/5] Set contractLocation Signed-off-by: Alexey Semenyuk --- internal/tezos/get_receipt.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/tezos/get_receipt.go b/internal/tezos/get_receipt.go index efccaca..fa78644 100644 --- a/internal/tezos/get_receipt.go +++ b/internal/tezos/get_receipt.go @@ -120,6 +120,12 @@ func (c *tezosConnector) TransactionReceipt(ctx context.Context, req *ffcapi.Tra operationReceipts = append(operationReceipts, extraInfo) fullReceipt, _ = json.Marshal(operationReceipts) + } else if o.Kind() == tezos.OpTypeOrigination { + res := o.(*rpc.Origination).Result() + if len(res.OriginatedContracts) > 0 { + contractAddress := res.OriginatedContracts[0].ContractAddress() + receiptResponse.ContractLocation = fftypes.JSONAnyPtrBytes([]byte(contractAddress)) + } } } From 53aee269e923962b3f440e1530ab9a9f083e5b27 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Wed, 20 Mar 2024 13:13:03 +0500 Subject: [PATCH 4/5] Fix lint issue Signed-off-by: Alexey Semenyuk --- internal/tezos/deploy_contract_prepare.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/tezos/deploy_contract_prepare.go b/internal/tezos/deploy_contract_prepare.go index 857447c..9def61d 100644 --- a/internal/tezos/deploy_contract_prepare.go +++ b/internal/tezos/deploy_contract_prepare.go @@ -20,10 +20,7 @@ func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi. return nil, ffcapi.ErrorReasonInvalidInputs, i18n.NewError(ctx, "Missing contract", req.Contract) } - sc, err := asScript(req.Contract.String()) - if err != nil { - return nil, ffcapi.ErrorReasonInvalidInputs, err - } + sc := asScript(req.Contract.String()) orig := &codec.Origination{ Script: sc, } @@ -56,8 +53,8 @@ func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi. }, "", nil } -func asScript(s string) (micheline.Script, error) { +func asScript(s string) micheline.Script { var sc micheline.Script _ = json.Unmarshal([]byte(s), &sc) - return sc, nil + return sc } From 40c82b2f11c1d8da2c667111be43f26fae081780 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 21 Mar 2024 20:03:43 +0500 Subject: [PATCH 5/5] Minor fixes Signed-off-by: Alexey Semenyuk --- internal/tezos/deploy_contract_prepare.go | 15 +++---- internal/tezos/get_receipt.go | 53 ++++++++++++++++++++--- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/internal/tezos/deploy_contract_prepare.go b/internal/tezos/deploy_contract_prepare.go index 9def61d..216f667 100644 --- a/internal/tezos/deploy_contract_prepare.go +++ b/internal/tezos/deploy_contract_prepare.go @@ -20,7 +20,8 @@ func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi. return nil, ffcapi.ErrorReasonInvalidInputs, i18n.NewError(ctx, "Missing contract", req.Contract) } - sc := asScript(req.Contract.String()) + var sc micheline.Script + _ = json.Unmarshal([]byte(req.Contract.String()), &sc) orig := &codec.Origination{ Script: sc, } @@ -30,11 +31,11 @@ func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi. return nil, ffcapi.ErrorReasonInvalidInputs, i18n.NewError(ctx, msgs.MsgInvalidFromAddress, req.From, err) } - hash, _ := c.client.GetBlockHash(ctx, rpc.Head) + headBlockHash, _ := c.client.GetBlockHash(ctx, rpc.Head) op := codec.NewOp(). WithContents(orig). WithSource(addr). - WithBranch(hash) + WithBranch(headBlockHash) err = c.completeOp(ctx, op, req.From, req.Nonce) if err != nil { @@ -45,16 +46,10 @@ func (c *tezosConnector) DeployContractPrepare(ctx context.Context, req *ffcapi. return nil, reason, err } - log.L(ctx).Infof("Prepared deploy transaction dataLen=%d gas=%s", len(op.Bytes()), req.Gas.Int()) + log.L(ctx).Infof("Prepared deploy transaction dataLen=%d", len(op.Bytes())) return &ffcapi.TransactionPrepareResponse{ Gas: req.Gas, TransactionData: hex.EncodeToString(op.Bytes()), }, "", nil } - -func asScript(s string) micheline.Script { - var sc micheline.Script - _ = json.Unmarshal([]byte(s), &sc) - return sc -} diff --git a/internal/tezos/get_receipt.go b/internal/tezos/get_receipt.go index fa78644..267e272 100644 --- a/internal/tezos/get_receipt.go +++ b/internal/tezos/get_receipt.go @@ -12,6 +12,8 @@ import ( "github.com/hyperledger/firefly-transaction-manager/pkg/ffcapi" ) +const _address = "address" + type receiptExtraInfo struct { ContractAddress *tezos.Address `json:"contractAddress"` ConsumedGas *fftypes.FFBigInt `json:"consumedGas"` @@ -91,7 +93,7 @@ func (c *tezosConnector) TransactionReceipt(ctx context.Context, req *ffcapi.Tra var script *micheline.Script if tx.Destination.IsContract() { location, _ := json.Marshal(map[string]string{ - "address": tx.Destination.String(), + _address: tx.Destination.String(), }) receiptResponse.ContractLocation = fftypes.JSONAnyPtrBytes(location) extraInfo.ContractAddress = &tx.Destination @@ -121,11 +123,15 @@ func (c *tezosConnector) TransactionReceipt(ctx context.Context, req *ffcapi.Tra operationReceipts = append(operationReceipts, extraInfo) fullReceipt, _ = json.Marshal(operationReceipts) } else if o.Kind() == tezos.OpTypeOrigination { - res := o.(*rpc.Origination).Result() - if len(res.OriginatedContracts) > 0 { - contractAddress := res.OriginatedContracts[0].ContractAddress() - receiptResponse.ContractLocation = fftypes.JSONAnyPtrBytes([]byte(contractAddress)) + result := o.(*rpc.Origination).Result() + originatedContracts := result.OriginatedContracts + if len(originatedContracts) > 0 { + location, _ := json.Marshal(map[string]string{ + _address: originatedContracts[0].ContractAddress(), + }) + receiptResponse.ContractLocation = fftypes.JSONAnyPtrBytes(location) } + fullReceipt = c.extraInfoForDeployTransactionReceipt(ctx, result, operationReceipts) } } @@ -134,3 +140,40 @@ func (c *tezosConnector) TransactionReceipt(ctx context.Context, req *ffcapi.Tra return receiptResponse, "", nil } + +func (c *tezosConnector) extraInfoForDeployTransactionReceipt(ctx context.Context, res rpc.OperationResult, operationReceipts []receiptExtraInfo) []byte { + status := res.Status.String() + extraInfo := receiptExtraInfo{ + ConsumedGas: fftypes.NewFFBigInt(res.ConsumedMilliGas / 1000), + StorageSize: fftypes.NewFFBigInt(res.StorageSize), + PaidStorageSizeDiff: fftypes.NewFFBigInt(res.PaidStorageSizeDiff), + Status: &status, + } + + if len(res.Errors) > 0 { + errorMessage := "" + for _, err := range res.Errors { + errorMessage += err.Error() + } + extraInfo.ErrorMessage = &errorMessage + } + + if prim := res.Storage; prim != nil { + val := micheline.NewValue(res.Storage.BuildType(), *prim) + m, err := val.Map() + if err != nil { + log.L(ctx).Error("error parsing contract storage: ", err) + } + storageBytes, _ := json.Marshal(m) + extraInfo.Storage = fftypes.JSONAnyPtrBytes(storageBytes) + } + + if len(res.OriginatedContracts) > 0 { + extraInfo.ContractAddress = &res.OriginatedContracts[0] + } + + operationReceipts = append(operationReceipts, extraInfo) + fullReceipt, _ := json.Marshal(operationReceipts) + + return fullReceipt +}