Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

Inefficient Storage Reads in Constructor Causing Elevated Deployment Gas Costs

Root + Impact

Inefficient Storage Reads Causes Expensive Deployment Gas Costs

Description

  • In the Game contract’s constructor, after validating and storing the incoming parameters (_initialClaimFee, _gracePeriod, etc.), the code re-reads those newly-written storage variables (initialClaimFee and initialGracePeriod) in order to initialize claimFee and gracePeriod.

  • Because storage reads are expensive (2100 gas cold, 100 gas warm) compared to using the constructor’s memory parameters directly, this pattern unnecessarily increases deployment gas. By reading from initialClaimFee and initialGracePeriod (storage), instead of using _initialClaimFee and _gracePeriod (stack/memory), each deployment pays extra gas for two storage loads.

initialClaimFee = _initialClaimFee;
initialGracePeriod = _gracePeriod;
// …
@> claimFee = initialClaimFee; // reads from storage
@> gracePeriod = initialGracePeriod; // reads from storage

Risk

Likelihood: High but once

  • Location: Game.sol -> constructor

  • Issue: After writing initialClaimFee and initialGracePeriod to storage, the constructor immediately reads them back in order to set claimFee and gracePeriod.

  • Gas Costs:

    • SSTORE writing initialClaimFee and initialGracePeriod: unavoidable.

    • SLOAD reading them back: avoidable by using the original parameters.

Impact: Low

  • Higher Deployment Fees: Every contract deployment pays extra gas for redundant storage loads.

  • Cumulative Cost: In large systems with many deployments (e.g., factory patterns, upgradable proxies), these inefficiencies compound.

  • Bad Practice Propagation: Encourages a pattern of reading back from storage when original data is still available in memory.

  • Gas Griefing Potential: Though not directly exploitable for theft, high base gas costs can deter legitimate use or be weaponized in DOS campaigns (e.g., forcing expensive redeployments in automated scripts).

Tools Used:

  • Foundry Test Suite

  • Chat-GPT AI Assistance (Report Grammar Check & Improvements)

  • Manual Review

Proof of Concept

function test_ConstructorHaveGasInconsistentStorageReReads() public {
uint256 gasPrice = 220_000_000;
vm.txGasPrice(gasPrice);
uint256 gasLeftBeforeDeployment = gasleft();
// NOTE: first test this testing function simple
// after then, do the following mutation into the constructor...
// // claimFee = initialClaimFee; // comment out this LoC
// claimFee = _initialClaimFee; // comment in this LoC
// gracePeriod = initialGracePeriod; // comment out this LoC
// gracePeriod = _gracePeriod; // comment in this LoC
// After the mutation re-run/test this testing function and see the difference in GAS and its cost
new Game(INITIAL_CLAIM_FEE, GRACE_PERIOD, FEE_INCREASE_PERCENTAGE, PLATFORM_FEE_PERCENTAGE);
uint256 gasLeftAfterDeployment = gasleft();
// 1960757 before mutation
// 1960557 after mutation
uint256 gasUsed = gasLeftBeforeDeployment - gasLeftAfterDeployment;
// 000_431_366_540_000_000 WEI or 0.00043136654 ETH
// 000_431_322_540_000_000 WEI or 0.00043132254 ETH
uint256 totalGasCost = gasUsed * tx.gasprice;
console2.log("gas left before deployment: ", gasLeftBeforeDeployment);
console2.log("gas left after deployment : ", gasLeftAfterDeployment);
console2.log("gas used : ", gasUsed);
console2.log("total Gas Cost : ", totalGasCost);
// DELTA AFTER MUTATION: 44000 or 44K WEI or 0.000_000_000_000_044_000
}
  • Step 1: Add the following test to test/Game.t.sol:

  • Step 2: Paste the above code snippet ⬆️

  • Step 3: Run the test suite with the following command:

forge test --mt test_ConstructorHaveGasInconsistentStorageReReads --gas-report
  • Step 4: Observe the output gas report, which will show the difference in gas usage before and after the optimization.

Scenario

  1. Baseline: Deploy Game with the current constructor.

  2. Optimized: Modify the constructor to assign directly from parameters:

- claimFee = initialClaimFee;
+ claimFee = _initialClaimFee;
- gracePeriod = initialGracePeriod;
+ gracePeriod = _gracePeriod;

Recommended Mitigation

Use Constructor Parameters Directly
Replace:

- claimFee = initialClaimFee;
- gracePeriod = initialGracePeriod;

With:

+ claimFee = _initialClaimFee;
+ gracePeriod = _gracePeriod;
Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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