Assessment reports>Fairyring>High findings>Gas-price calculation error due to integer division rounding
Category: Coding Mistakes

Gas-price calculation error due to integer division rounding

High Severity
High Impact
High Likelihood

Description

The gas-fee calculation code may lead to inaccuracies due to rounding errors in integer division. Currently, the calculation sequence is as follows:

usedGasFee := sdk.NewCoin(
    txFee[0].Denom,
    // Tx Fee Amount Divide Provide Gas => provided gas price
    // Provided Gas Price * Gas Used => Amount to deduct as gas fee
    txFee[0].Amount.Quo(gasProvided).Mul(gasUsedInBig),
)

This calculation first divides txFee[0].Amount by gasProvided, both of which are integers. As a result, the division rounds down, potentially resulting in a loss of precision. For example, if txFee[0].Amount is 10,000 and gasProvided is 20,000, the result of 10,000 / 20,000 is 0. Multiplying by any gasUsedInBig value still results in zero, meaning the calculated gas fee could be significantly smaller than intended.

Impact

In this scenario, a malicious actor could exploit the rounding issue to avoid paying gas fees, potentially submitting numerous transactions at minimal or zero cost. This creates an imbalance by allowing a user to consume chain resources without bearing the intended transaction costs, leading to revenue loss and creating an unfair transaction environment.

The following POC demonstrates this issue in a controlled environment, showing how the chain calculates an incorrect gas fee of zero, even when gas was actually consumed. Using fairyringd, the commands below simulate a transaction with an encrypted payload and set gas price, highlighting the error in gas-fee deduction.

  1. Prepare a transaction for encryption to execute a WASM contract with a specified gas price of 0.5 ufairy.

fairyringd tx wasm execute $CONTRACT_ADDR '{"identity": "", "pubkey": "", "decryption_key": ""}' -y --home devnet_data/fairyring_devnet --from wallet1 --keyring-backend test --chain-id fairyring_devnet --generate-only --gas-prices 0.5ufairy > execute_tx_unsigned.json
  1. Sign the transaction.

fairyringd tx sign execute_tx_unsigned.json --home devnet_data/fairyring_devnet --from wallet1 --keyring-backend test --chain-id fairyring_devnet > execute_tx.json
  1. Encrypt the signed transaction for submission.

fairyringd encrypt $TARGET_HEIGHT '' "$(cat execute_tx.json)" --node tcp://localhost:26657 --home devnet_data/fairyring_devnet > execute_tx.hex
  1. Submit the encrypted transaction.

fairyringd tx pep submit-encrypted-tx $(cat execute_tx.hex) $TARGET_HEIGHT -y --home devnet_data/fairyring_devnet --from wallet1 --keyring-backend test --chain-id fairyring_devnet --gas-prices 1ufairy

Upon execution, the following log demonstrates the impact:

{"message":"Underlying tx consumed: 36596, decryption consumed: 95652"}
{"message":"gasUsedInBig: 132248, txFee[0].Amount: 100000, gasProvided: 200000, usedGasFee: 0"}
{"message":"ChargedGas: 300000"}
{"message":"Deduct fee amount: [<nil>] | Refund amount: 300000"}

In this example, despite gasUsedInBig showing 132,248 units of gas consumed, the usedGasFee is calculated as 0 due to early rounding in integer division. As a result, the fee amount deducted is [<nil>], while a full refund of 300,000 units is issued, effectively leading to no fee being charged for this transaction.

Recommendations

To avoid rounding errors, modify the calculation to multiply before dividing:

usedGasFee := sdk.NewCoin(
    txFee[0].Denom,
    txFee[0].Amount.Mul(gasUsedInBig).Quo(gasProvided),
)

This sequence maximizes precision and ensures the calculated gas fee accurately reflects the intended charge based on gasUsedInBig and gasProvided.

Alternatively, consider switching from integer division to safe floating-point division to avoid rounding issues.

Remediation

This issue has been acknowledged by Fairblock Inc., and a fix was implemented in commit 5b830510.

Zellic © 2025Back to top ↑