Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Severity: low
Valid

Precision Loss in Fee Calculations Leads to Minor Revenue Leakage

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.

// src/Game.sol
// ...
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// ...
claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;

Risk

Likelihood: High

  • This precision loss occurs in the calculation logic for every claimThrone transaction where the fee math results in a remainder.

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

// test/PrecisionLoss.t.sol
// SPDX-License-Identifier: MIT
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); // 3% platform fee
vm.stopPrank();
}
function test_PoC_FeeCalculationLosesWei() public {
// --- Setup ---
// Choose an amount that will cause rounding loss.
// For a 3% fee: (199 wei * 3) = 597.
// The contract will calculate `597 / 100 = 5`.
// The remainder of `97 wei` will be lost.
uint256 amountWithLoss = 199 wei;
uint256 platformFeePercentage = game.platformFeePercentage(); // 3
// --- Simulation & Assertion ---
// We simulate the exact calculation performed inside claimThrone()
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);
// This assertion proves the core of the vulnerability.
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.

Updates

Appeal created

inallhonesty Lead Judge 9 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Precision loss in fee calc

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.