From cfe754db45596d69d9255392ec51e20509420b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matev=C5=BE=20Jekovec?= Date: Mon, 21 Oct 2024 18:11:16 +0200 Subject: [PATCH] cmd: Add support for paratime transfers fees --- cmd/account/transfer.go | 26 ++++++++---- cmd/common/transaction.go | 88 ++++++++++++++++++++++----------------- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/cmd/account/transfer.go b/cmd/account/transfer.go index 0e245965..45308634 100644 --- a/cmd/account/transfer.go +++ b/cmd/account/transfer.go @@ -60,6 +60,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())) @@ -69,23 +70,22 @@ var transferCmd = &cobra.Command{ cobra.CheckErr("consensus layer only supports the native denomination") } - // Consensus layer transfer. - amount, err := helpers.ParseConsensusDenomination(npa.Network, amount) + amountBaseUnits, err := helpers.ParseConsensusDenomination(npa.Network, amount) cobra.CheckErr(err) // Prepare transaction. innerTx := staking.Transfer{ To: toAddr.ConsensusAddress(), - Amount: *amount, + Amount: *amountBaseUnits, } 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) cobra.CheckErr(err) - err = amount.Sub(fee) + err = amountBaseUnits.Sub(fee) cobra.CheckErr(err) - innerTx.Amount = *amount + innerTx.Amount = *amountBaseUnits tx = staking.NewTransferTx(0, nil, &innerTx) } @@ -97,10 +97,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, acc.Signer(), conn, tx) + 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) diff --git a/cmd/common/transaction.go b/cmd/common/transaction.go index 6e486c52..16a47076 100644 --- a/cmd/common/transaction.go +++ b/cmd/common/transaction.go @@ -118,7 +118,8 @@ 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) + // TODO: Should be moved under !txOffline + _, gas, fee, err := ComputeConsensusGas(ctx, npa, signer, conn, tx) if err != nil { return nil, err } @@ -168,8 +169,10 @@ 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) { +// 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) (gasPrice *quantity.Quantity, gas consensusTx.Gas, fee *quantity.Quantity, err error) { gasPrice = quantity.NewQuantity() if txGasPrice != "" { gasPrice, err = helpers.ParseConsensusDenomination(npa.Network, txGasPrice) @@ -195,6 +198,44 @@ func ComputeConsensusGasInfo(ctx context.Context, npa *NPASelection, signer core return } +// 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, signer signature.Signer, conn connection.Connection, tx *types.Transaction) (gasPrice *types.BaseUnits, gas consensusTx.Gas, fee *quantity.Quantity, feeDenom types.Denomination, err error) { + feeDenom = types.Denomination(txFeeDenom) + if txGasPrice != "" { + var err error + gasPrice, err = helpers.ParseParaTimeDenomination(npa.ParaTime, txGasPrice, feeDenom) + if err != nil { + return nil, 0, nil, "", fmt.Errorf("bad gas price: %w", err) + } + } + 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, 0, 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, 0, nil, "", fmt.Errorf("failed to query minimum gas price: %w", err) + } + + *gasPrice = types.NewBaseUnits(mgp[feeDenom], feeDenom) + } + fee = gasPrice.Amount.Clone() + + // Compute fee amount based on gas price. + if err := fee.Mul(quantity.NewFromUint64(tx.AuthInfo.Fee.Gas)); err != nil { + return nil, 0, nil, "", err + } + return +} + // SignParaTimeTransaction signs a ParaTime transaction. // // Returns the signed transaction and call format-specific metadata for result decoding. @@ -229,17 +270,6 @@ func SignParaTimeTransaction( 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 @@ -260,25 +290,9 @@ 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) - } - - *gasPrice = types.NewBaseUnits(mgp[feeDenom], feeDenom) - } + _, _, fee, feeDenom, err := ComputeParaTimeGas(ctx, npa, account.Signer(), conn, tx) + if err != nil { + return nil, nil, err } // If we are using offline mode and gas limit is not specified, abort. @@ -286,12 +300,8 @@ func SignParaTimeTransaction( 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 + tx.AuthInfo.Fee.Amount.Amount = *fee + tx.AuthInfo.Fee.Amount.Denomination = feeDenom // Handle confidential transactions. var meta interface{}