Skip to content

Commit

Permalink
Merge pull request #36 from alex-semenyuk/deploy_tezos_contract
Browse files Browse the repository at this point in the history
DeployContract implementation for Tezos connector
  • Loading branch information
nguyer authored Apr 8, 2024
2 parents cbfad5d + 40c82b2 commit 9def885
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 8 deletions.
49 changes: 46 additions & 3 deletions internal/tezos/deploy_contract_prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,54 @@ 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) {
if req.Contract == nil {
return nil, ffcapi.ErrorReasonInvalidInputs, i18n.NewError(ctx, "Missing contract", req.Contract)
}

var sc micheline.Script
_ = json.Unmarshal([]byte(req.Contract.String()), &sc)
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)
}

headBlockHash, _ := c.client.GetBlockHash(ctx, rpc.Head)
op := codec.NewOp().
WithContents(orig).
WithSource(addr).
WithBranch(headBlockHash)

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", len(op.Bytes()))

return &ffcapi.TransactionPrepareResponse{
Gas: req.Gas,
TransactionData: hex.EncodeToString(op.Bytes()),
}, "", nil
}
125 changes: 121 additions & 4 deletions internal/tezos/deploy_contract_prepare_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
51 changes: 50 additions & 1 deletion internal/tezos/get_receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -120,6 +122,16 @@ func (c *tezosConnector) TransactionReceipt(ctx context.Context, req *ffcapi.Tra

operationReceipts = append(operationReceipts, extraInfo)
fullReceipt, _ = json.Marshal(operationReceipts)
} else if o.Kind() == tezos.OpTypeOrigination {
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)
}
}

Expand All @@ -128,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
}

0 comments on commit 9def885

Please sign in to comment.