Gas-price calculation error due to integer division rounding
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.
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
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
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
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↗.