Description
The claimThrone()
function calculates percentage-based fees using integer division. Specifically, the platform fee and the subsequent increase in the claimFee
are calculated using the pattern (amount * percentage) / 100
.
This implementation leads to precision loss because any fractional result is truncated. This causes the protocol to consistently lose small amounts of wei
on every transaction where the fee calculation is not a perfect integer. While the amount per transaction is small, it represents a persistent and preventable revenue leak.
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
Risk
Likelihood: High
Impact: Low
-
The protocol loses a small amount of wei
revenue on many transactions.
-
This represents a design flaw in handling financial calculations and leads to a slow, cumulative drain of value.
Proof of Concept
The following test isolates and verifies the flawed calculation logic. It proves that for an input amount that is not a clean multiple, the internal fee calculation will result in a loss of wei
due to rounding down.
Test File: test/PrecisionLoss.t.sol
pragma solidity ^0.8.20;
import {Test, console2} from "forge-std/Test.sol";
import {Game} from "../../src/Game.sol";
contract PrecisionLossTest is Test {
Game public game;
address public deployer;
function setUp() public {
deployer = makeAddr("deployer");
vm.deal(deployer, 1 ether);
vm.startPrank(deployer);
game = new Game(0.01 ether, 1 days, 10, 3);
vm.stopPrank();
}
function test_PoC_FeeCalculationLosesWei() public {
uint256 amountWithLoss = 199 wei;
uint256 platformFeePercentage = game.platformFeePercentage();
uint256 feeCalculatedByContract = (amountWithLoss * platformFeePercentage) / 100;
uint256 weiLost = (amountWithLoss * platformFeePercentage) % 100;
console2.log("Input Amount (wei):", amountWithLoss);
console2.log("Fee Percentage:", platformFeePercentage);
console2.log("Fee Collected by Contract (wei):", feeCalculatedByContract);
console2.log("Wei Lost to Rounding:", weiLost);
assertTrue(weiLost > 0, "VULNERABILITY: Wei was lost due to rounding down in fee calculation.");
assertEq(feeCalculatedByContract, 5, "Contract correctly calculates the truncated fee as 5 wei.");
assertEq(weiLost, 97, "Exactly 97 wei were lost to rounding.");
}
}
Successful Test Output:
$ forge test --match-path test/PrecisionLoss.t.sol -vv
Ran 1 test for test/PrecisionLoss.t.sol:PrecisionLossTest
[PASS] test_PoC_FeeCalculationLosesWei() (gas: 20592)
Logs:
Input Amount (wei): 199
Fee Percentage: 3
Fee Collected by Contract (wei): 5
Wei Lost to Rounding: 97
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 660.25µs (100.70µs CPU time)
The test passes, confirming that the calculation logic inside the contract is flawed and leads to a loss of wei
, thus validating the vulnerability.
Recommended Mitigation
To ensure more accurate financial calculations, it is recommended to increase the precision of percentage calculations. The industry standard is to use basis points, where 10000
represents 100%.
// In src/Game.sol
// In constructor and state variables
- uint256 public feeIncreasePercentage;
- uint256 public platformFeePercentage;
+ uint256 public feeIncreaseBps;
+ uint256 public platformFeeBps;
// ... (update constructor, setters, and events accordingly) ...
// In claimThrone function
- currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
+ currentPlatformFee = (sentAmount * platformFeeBps) / 10000;
- claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
+ claimFee = claimFee + (claimFee * feeIncreaseBps) / 10000;
This change significantly reduces rounding errors and ensures the protocol captures revenue more accurately.