Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

State variables in the `Snow.sol` contract are not designated as immutable, costing more gas for functions that use the variable

State variables in the `Snow.sol` contract are not designated as immutable, costing more gas for functions that use the variable

Description

`Snow.sol` contains state variables that are intialized in the constructor, but never change value through setter functions or other function interactions.

In `Snow.sol`:

uint256 public s_buyFee;
...
IERC20 i_weth;

Risk

Likelihood:

  • The likelihood is high because every time the variables are loaded the use more gas than they would have if they had been declared immutable

Impact:

Failing to assign these variables as immutable results in more gas use for users. Immutable variables are stored directly in the contract runtime bytecode as opposed to having to be loaded using an `SLOAD` operation.

Proof of Concept


Add the following contracts to `TestSnow.t.sol`:

contract ImmutableVariableContract {
address public immutable i_weth;
constructor(address _weth) {
i_weth = _weth;
}
function read() external view returns (address) {
return i_weth; // Triggers PUSH (inline bytecode data)
}
}
contract NotImmutableVariableContract {
address public i_weth;
constructor(address _weth) {
i_weth = _weth;
}
function read() external view returns (address) {
return i_weth; // Triggers SLOAD (storage read)
}
}

Add the contracts as veriables to the `TestSnow` contract:


ImmutableVariableContract immutableInstance;
NotImmutableVariableContract notImmutableInstance;

Initialize the contracts with the same WETH address value in the constructor:


immutableInstance = new ImmutableVariableContract(address(weth));
notImmutableInstance = new NotImmutableVariableContract(address(weth));

Add the following test to the test suite:


function test_compareGasCosts() public view {
// 1. Measure Storage read gas
uint256 gasBeforeStorage = gasleft();
address val1 = notImmutableInstance.read();
uint256 gasUsedStorage = gasBeforeStorage - gasleft();
// 2. Measure Immutable read gas
uint256 gasBeforeImmutable = gasleft();
address val2 = immutableInstance.read();
uint256 gasUsedImmutable = gasBeforeImmutable - gasleft();
// Keep values in scope to ensure compiler doesn't discard reads
vm.assume(val1 == val2);
// 3. Print out execution gas results
console2.log("-----------------------------------------");
console2.log("Gas used by STORAGE read: ", gasUsedStorage);
console2.log("Gas used by IMMUTABLE read:", gasUsedImmutable);
console2.log("Gas saved by using Immutable:", gasUsedStorage - gasUsedImmutable);
console2.log("-----------------------------------------");
// Assert that the storage read cost is higher than the immutable read
assertTrue(gasUsedStorage > gasUsedImmutable, "Storage must use more gas than immutable");
}

Run the test in the terminal:

forge test --mt test_compareGasCosts -vvvv

If the test passes, then the storage variable uses more gas to load.


Recommended Mitigation


Declare the variables as immutable to save gas costs:

In Snow.sol:

- uint256 public s_buyFee;
+ uint256 public immutable s_buyFee;
...
- IERC20 i_weth;
+ IERC20 immutable i_weth;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!