Skip to content

Commit

Permalink
cmd: Add support for paratime transfers fees
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Oct 25, 2024
1 parent fe6ea46 commit 9bfa8f0
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 85 deletions.
2 changes: 2 additions & 0 deletions cmd/account/show/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ func prettyPrintAccountBalanceAndDelegationsFrom(
}
_ = totalAmount.Add(&totalDebDelegationsAmount)

txt, _ := generalAccount.Balance.MarshalText()
fmt.Printf("%s\n", txt)
fmt.Fprintf(w, "%sTotal: ", prefix)
fmt.Fprintf(w, "%s\n", helpers.FormatConsensusDenomination(network, *totalAmount))
fmt.Fprintf(w, "%sAvailable: ", prefix)
Expand Down
31 changes: 23 additions & 8 deletions cmd/account/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package account

import (
"context"
"fmt"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -60,6 +61,7 @@ var transferCmd = &cobra.Command{
var sigTx, meta interface{}
switch npa.ParaTime {
case nil:
// Consensus layer transfer.
common.CheckForceErr(common.CheckAddressIsConsensusCapable(cfg, toAddr.String()))
if toEthAddr != nil {
common.CheckForceErr(common.CheckAddressIsConsensusCapable(cfg, toEthAddr.Hex()))
Expand All @@ -69,23 +71,26 @@ var transferCmd = &cobra.Command{
cobra.CheckErr("consensus layer only supports the native denomination")
}

// Consensus layer transfer.
amount, err := helpers.ParseConsensusDenomination(npa.Network, amount)
amt, err := helpers.ParseConsensusDenomination(npa.Network, amount)
cobra.CheckErr(err)

// Prepare transaction.
innerTx := staking.Transfer{
To: toAddr.ConsensusAddress(),
Amount: *amount,
Amount: *amt,
}
tx := staking.NewTransferTx(0, nil, &innerTx)

if subtractFee {
_, _, fee, err := common.ComputeConsensusGasInfo(ctx, npa, acc.ConsensusSigner(), conn, tx)
_, fee, err := common.ComputeConsensusGas(ctx, npa, acc.ConsensusSigner(), conn, tx)

Check failure on line 85 in cmd/account/transfer.go

View workflow job for this annotation

GitHub Actions / lint

shadow: declaration of "err" shadows declaration at line 74 (govet)
cobra.CheckErr(err)
err = amount.Sub(fee)
txt, _ := fee.MarshalText()
fmt.Printf("%s\n", txt)
fmt.Printf("before %s\n", amt)
err = amt.Sub(fee)
fmt.Printf("after %s\n", amt)
cobra.CheckErr(err)
innerTx.Amount = *amount
innerTx.Amount = *amt
tx = staking.NewTransferTx(0, nil, &innerTx)
}

Expand All @@ -97,10 +102,20 @@ var transferCmd = &cobra.Command{
cobra.CheckErr(err)

// Prepare transaction.
tx := accounts.NewTransferTx(nil, &accounts.Transfer{
innerTx := accounts.Transfer{
To: *toAddr,
Amount: *amountBaseUnits,
})
}
tx := accounts.NewTransferTx(nil, &innerTx)

if subtractFee {
_, fee, _, err := common.ComputeParaTimeGas(ctx, npa, conn, tx)

Check failure on line 112 in cmd/account/transfer.go

View workflow job for this annotation

GitHub Actions / lint

shadow: declaration of "err" shadows declaration at line 101 (govet)
cobra.CheckErr(err)
err = amountBaseUnits.Amount.Sub(fee)
cobra.CheckErr(err)
innerTx.Amount = *amountBaseUnits
tx = accounts.NewTransferTx(nil, &innerTx)
}

txDetails := sdkSignature.TxDetails{OrigTo: toEthAddr}
sigTx, meta, err = common.SignParaTimeTransaction(ctx, npa, acc, conn, tx, &txDetails)
Expand Down
14 changes: 12 additions & 2 deletions cmd/account/withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,20 @@ var withdrawCmd = &cobra.Command{
cobra.CheckErr(err)

// Prepare transaction.
tx := consensusaccounts.NewWithdrawTx(nil, &consensusaccounts.Withdraw{
innerTx := consensusaccounts.Withdraw{
To: toAddr,
Amount: *amountBaseUnits,
})
}
tx := consensusaccounts.NewWithdrawTx(nil, &innerTx)

if subtractFee {
_, fee, _, err := common.ComputeParaTimeGas(ctx, npa, conn, tx)

Check failure on line 85 in cmd/account/withdraw.go

View workflow job for this annotation

GitHub Actions / lint

shadow: declaration of "err" shadows declaration at line 74 (govet)
cobra.CheckErr(err)
err = amountBaseUnits.Amount.Sub(fee)
cobra.CheckErr(err)
innerTx.Amount = *amountBaseUnits
tx = consensusaccounts.NewWithdrawTx(nil, &innerTx)
}

acc := common.LoadAccount(cfg, npa.AccountName)
sigTx, meta, err := common.SignParaTimeTransaction(ctx, npa, acc, conn, tx, nil)
Expand Down
165 changes: 90 additions & 75 deletions cmd/common/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,42 @@ func isRuntimeTx(tx interface{}) bool {
return isRuntimeTx
}

// ComputeConsensusGas estimates gas for the given consensus transaction.
//
// Returns the gas price, gas limit and total fee amount.
func ComputeConsensusGas(ctx context.Context, npa *NPASelection, signer coreSignature.Signer, conn connection.Connection, tx *consensusTx.Transaction) (gas consensusTx.Gas, fee *quantity.Quantity, err error) {
// Gas price estimation if not specified.
gasPrice := quantity.NewQuantity()
if txGasPrice != "" {
gasPrice, err = helpers.ParseConsensusDenomination(npa.Network, txGasPrice)
if err != nil {
err = fmt.Errorf("bad gas price: %w", err)
return

Check failure on line 101 in cmd/common/transaction.go

View workflow job for this annotation

GitHub Actions / lint

naked return in func `ComputeConsensusGas` with 31 lines of code (nakedret)
}
}

// Gas limit estimation if not specified.
gas = consensusTx.Gas(txGasLimit)
if !txOffline && gas == invalidGasLimit {
gas, err = conn.Consensus().EstimateGas(ctx, &consensus.EstimateGasRequest{
Signer: signer.Public(),
Transaction: tx,
})
if err != nil {
err = fmt.Errorf("failed to estimate gas: %w", err)
return

Check failure on line 114 in cmd/common/transaction.go

View workflow job for this annotation

GitHub Actions / lint

naked return in func `ComputeConsensusGas` with 31 lines of code (nakedret)
}
}

// Compute fee.
fee = gasPrice.Clone()
if err = fee.Mul(quantity.NewFromUint64(uint64(gas))); err != nil {
err = fmt.Errorf("failed to compute gas fee: %w", err)
return

Check failure on line 122 in cmd/common/transaction.go

View workflow job for this annotation

GitHub Actions / lint

naked return in func `ComputeConsensusGas` with 31 lines of code (nakedret)
}
return
}

// SignConsensusTransaction signs a consensus transaction.
func SignConsensusTransaction(
ctx context.Context,
Expand Down Expand Up @@ -118,37 +154,35 @@ func SignConsensusTransaction(
return nil, fmt.Errorf("consensus layer only supports the native denomination for paying fees")
}

_, gas, fee, err := ComputeConsensusGasInfo(ctx, npa, signer, conn, tx)
gas, fee, err := ComputeConsensusGas(ctx, npa, signer, conn, tx)
txt, _ := fee.MarshalText()
fmt.Printf("tx fee %s\n", txt)
if err != nil {
return nil, err
}

if !txOffline { //nolint: nestif
// Query nonce if not specified.
if tx.Nonce == invalidNonce {
nonce, err := conn.Consensus().GetSignerNonce(ctx, &consensus.GetSignerNonceRequest{
AccountAddress: wallet.Address().ConsensusAddress(),
Height: consensus.HeightLatest,
})
if err != nil {
return nil, fmt.Errorf("failed to query nonce: %w", err)
}
tx.Nonce = nonce
}
if tx.Fee.Gas == invalidGasLimit {
tx.Fee.Gas = gas
tx.Fee.Amount = *fee
}

// Gas estimation if not specified.
if tx.Fee.Gas == invalidGasLimit {
tx.Fee.Gas = gas
// Query nonce if not specified.
if !txOffline && tx.Nonce == invalidNonce {
nonce, err := conn.Consensus().GetSignerNonce(ctx, &consensus.GetSignerNonceRequest{

Check failure on line 171 in cmd/common/transaction.go

View workflow job for this annotation

GitHub Actions / lint

shadow: declaration of "err" shadows declaration at line 157 (govet)
AccountAddress: wallet.Address().ConsensusAddress(),
Height: consensus.HeightLatest,
})
if err != nil {
return nil, fmt.Errorf("failed to query nonce: %w", err)
}
tx.Nonce = nonce
}

// If we are using offline mode and either nonce or gas limit is not specified, abort.
if tx.Nonce == invalidNonce || tx.Fee.Gas == invalidGasLimit {
return nil, fmt.Errorf("nonce and/or gas limit must be specified in offline mode")
}

tx.Fee.Amount = *fee

if txUnsigned {
// Return an unsigned transaction.
return tx, nil
Expand All @@ -168,29 +202,43 @@ func SignConsensusTransaction(
return &consensusTx.SignedTransaction{Signed: *signed}, nil
}

// ComputeConsensusGasInfo estimates and returns the gas limit, gas price and derived fee amount. It assumes CLI parameters and the existing transaction properties.
func ComputeConsensusGasInfo(ctx context.Context, npa *NPASelection, signer coreSignature.Signer, conn connection.Connection, tx *consensusTx.Transaction) (gasPrice *quantity.Quantity, gas consensusTx.Gas, fee *quantity.Quantity, err error) {
gasPrice = quantity.NewQuantity()
// ComputeParaTimeGas estimates gas for the given ParaTime transaction.
//
// Returns the gas price, gas limit and total fee amount.
func ComputeParaTimeGas(ctx context.Context, npa *NPASelection, conn connection.Connection, tx *types.Transaction) (gas uint64, fee *quantity.Quantity, feeDenom types.Denomination, err error) {
// Gas price estimation if not specified.
gasPrice := &types.BaseUnits{}
feeDenom = types.Denomination(txFeeDenom)
if txGasPrice != "" {
gasPrice, err = helpers.ParseConsensusDenomination(npa.Network, txGasPrice)
gasPrice, err = helpers.ParseParaTimeDenomination(npa.ParaTime, txGasPrice, feeDenom)
if err != nil {
err = fmt.Errorf("bad gas price: %w", err)
return

Check failure on line 216 in cmd/common/transaction.go

View workflow job for this annotation

GitHub Actions / lint

naked return in func `ComputeParaTimeGas` with 36 lines of code (nakedret)
}
} else if !txOffline {
var mgp map[types.Denomination]types.Quantity
mgp, err = conn.Runtime(npa.ParaTime).Core.MinGasPrice(ctx)
if err != nil {
return nil, 0, nil, fmt.Errorf("bad gas price: %w", err)
err = fmt.Errorf("failed to query minimum gas price: %w", err)
return

Check failure on line 223 in cmd/common/transaction.go

View workflow job for this annotation

GitHub Actions / lint

naked return in func `ComputeParaTimeGas` with 36 lines of code (nakedret)
}
*gasPrice = types.NewBaseUnits(mgp[feeDenom], feeDenom)
}
gas = consensusTx.Gas(txGasLimit)
if gas == invalidGasLimit {
gas, err = conn.Consensus().EstimateGas(ctx, &consensus.EstimateGasRequest{
Signer: signer.Public(),
Transaction: tx,
})

// Gas limit estimation if not specified.
gas = txGasLimit
if gas == invalidGasLimit && !txOffline {
gas, err = conn.Runtime(npa.ParaTime).Core.EstimateGas(ctx, client.RoundLatest, tx, false)
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to estimate gas: %w", err)
err = fmt.Errorf("failed to estimate gas: %w", err)
return

Check failure on line 234 in cmd/common/transaction.go

View workflow job for this annotation

GitHub Actions / lint

naked return in func `ComputeParaTimeGas` with 36 lines of code (nakedret)
}
}
// Compute fee amount based on gas price.
fee = gasPrice.Clone()
if err = fee.Mul(quantity.NewFromUint64(uint64(gas))); err != nil {
return nil, 0, nil, fmt.Errorf("failed to compute gas fee: %w", err)

// Compute fee.
fee = gasPrice.Amount.Clone()
if err = fee.Mul(quantity.NewFromUint64(gas)); err != nil {
return
}
return
}
Expand Down Expand Up @@ -224,22 +272,6 @@ func SignParaTimeTransaction(
break
}

// Default to passed values and do online estimation when possible.
if tx.AuthInfo.Fee.Gas == 0 {
tx.AuthInfo.Fee.Gas = txGasLimit
}

feeDenom := types.Denomination(txFeeDenom)

gasPrice := &types.BaseUnits{}
if txGasPrice != "" {
var err error
gasPrice, err = helpers.ParseParaTimeDenomination(npa.ParaTime, txGasPrice, feeDenom)
if err != nil {
return nil, nil, fmt.Errorf("bad gas price: %w", err)
}
}

if !hasSignerInfo {
nonce := txNonce

Expand All @@ -260,39 +292,22 @@ func SignParaTimeTransaction(
tx.AppendAuthSignature(account.SignatureAddressSpec(), nonce)
}

if !txOffline { //nolint: nestif
// Gas estimation if not specified.
if tx.AuthInfo.Fee.Gas == invalidGasLimit {
var err error
tx.AuthInfo.Fee.Gas, err = conn.Runtime(npa.ParaTime).Core.EstimateGas(ctx, client.RoundLatest, tx, false)
if err != nil {
return nil, nil, fmt.Errorf("failed to estimate gas: %w", err)
}
}

// Gas price determination if not specified.
if txGasPrice == "" {
mgp, err := conn.Runtime(npa.ParaTime).Core.MinGasPrice(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed to query minimum gas price: %w", err)
}
gas, fee, feeDenom, err := ComputeParaTimeGas(ctx, npa, conn, tx)
if err != nil {
return nil, nil, err
}

*gasPrice = types.NewBaseUnits(mgp[feeDenom], feeDenom)
}
if tx.AuthInfo.Fee.Gas == 0 {
tx.AuthInfo.Fee.Gas = gas
tx.AuthInfo.Fee.Amount.Amount = *fee
tx.AuthInfo.Fee.Amount.Denomination = feeDenom
}

// If we are using offline mode and gas limit is not specified, abort.
if tx.AuthInfo.Fee.Gas == invalidGasLimit {
return nil, nil, fmt.Errorf("gas limit must be specified in offline mode")
}

// Compute fee amount based on gas price.
if err := gasPrice.Amount.Mul(quantity.NewFromUint64(tx.AuthInfo.Fee.Gas)); err != nil {
return nil, nil, err
}
tx.AuthInfo.Fee.Amount.Amount = gasPrice.Amount
tx.AuthInfo.Fee.Amount.Denomination = gasPrice.Denomination

// Handle confidential transactions.
var meta interface{}
if txEncrypted {
Expand Down

0 comments on commit 9bfa8f0

Please sign in to comment.