Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $36,500 USDC
Total HM: 9
Participants: 80
Period: 7 days
Judge: hansfriese
Total Solo HM: 2
Id: 332
League: ETH
Rank: 46/80
Findings: 1
Award: $19.37
š Selected for report: 0
š Solo Findings: 0
š Selected for report: slvDev
Also found by: 0x11singh99, 0xhacksmithh, SAQ, SY_S, albahaca, dharma09, hunter_w3b, shamsulhaq123, unique
19.3678 USDC - $19.37
no | Issue | Instances | |
---|---|---|---|
[G-01] | Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead | 2 | |
[G-02] | UsingĀ PRIVATEĀ rather thanĀ PUBLICĀ FOR Immutable, Saves Gas | 2 | |
[G-03] | not using the named return variable when a function returns, wastes deployment gas | 1 | |
[G-04] | Use assembly to check for address(0) | 6 | |
[G-05] | Before transfer of some functions, we should check some variables for possible gas save | 2 | |
[G-06] | Remove theĀ initializerĀ modifier | 2 | |
[G-07] | Structs can be packed into fewer storage slots by editing time variables | 1 | |
[G-08] | Use assembly to emit events | 13 | |
[G-09] | Sort Solidity operations using short-circuit mode | 1 | |
[G-10] | Use hardcode address instead address(this) | 14 | |
[G-11] | Use Mappings Instead of Arrays. | 1 | |
[G-12] | Use assembly for math (add, sub, mul, div) | 3 | |
[G-13] | Using unchecked blocks to save gas | 6 | |
[G-14] | Use assembly for small keccak256 hashes, in order to save gas | 1 | |
[G-15] | Use assembly to validate msg.sender | 1 |
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
file:/src/TwabERC20.sol 101 twabController.transfer(_from, _to, SafeCast.toUint96(_amount));
File:/src/abstract/Claimable.sol 21 uint24 public constant HOOK_GAS = 150_000;
If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table
file: /src/TwabERC20.sol 26 TwabController public immutable twabController;
file:/src/abstract/Claimable.sol 24 PrizePool public immutable prizePool;
When you execute a function that returns values in Solidity, the EVM still performs the necessary operations to execute and return those values. This includes the cost of allocating memory and packing the return values. If the returned values are not utilized, it can be seen as wasteful since you are incurring gas costs for operations that have no effect.
file:/src/PrizeVaultFactory.sol ) external returns (PrizeVault) {
Save 6 gas per instance.
file: /src/PrizeVault.sol 300 if (address(yieldVault_) == address(0)) revert YieldVaultZeroAddress(); 301 if (owner_ == address(0)) revert OwnerZeroAddress(); 743 if (address(_liquidationPair) == address(0)) revert LPZeroAddress();
file:/src/abstract/Claimable.sol 65 if (address(prizePool_) == address(0)) revert PrizePoolZeroAddress(); 97 if (recipient == address(0)) revert ClaimRecipientZeroAddress(); 129 if (_claimer == address(0)) revert ClaimerZeroAddress();
Before transfer, we should check for amount being 0 so the function doesn't run when its not gonna do anything:
file:/src/PrizeVault.sol 939 _asset.transfer(_receiver, _assets);
file:/src/TwabERC20.sol 78 emit Transfer(address(0), _receiver, _amount);
If we can just ensure that theĀ initialize()Ā function could only be called from within the constructor, we shouldnāt need to worry about it getting called again.
file:/src/TwabERC20.sol 48 twabController = twabController_;
file:/src/abstract/Claimable.sol 66 prizePool = prizePool_;
The following structures could be optimized moving the position of certain values in order to save a lot slots:
Enums are represented by integers; the possibility listed first by 0, the next by 1, and so forth. An enum type just acts like uintN, where N is the smallest legal value large enough to accomodate all the possibilities.
file: 8 struct VaultHooks { 0 bool useBeforeClaimPrize; 10 bool useAfterClaimPrize; 11 IVaultHooks implementation;
We can use assembly to emit events efficiently by utilizingĀ scratch spaceĀ and theĀ free memory pointer. This will allow us to potentially avoid memory expansion costs. Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.
file:/src/PrizeVault.sol 562 emit Sponsor(_owner, _assets, _shares); 621 emit ClaimYieldFeeShares(msg.sender, _shares); 697 emit TransferYieldOut(msg.sender, _tokenOut, _receiver, _amountOut, _yieldFee); 747 emit LiquidationPairSet(address(this), address(_liquidationPair)); 876 emit Deposit(_caller, _receiver, _assets, _shares); 909 emit Withdraw(_caller, _receiver, _owner, _assets, _shares); 952 emit YieldFeePercentageSet(_yieldFeePercentage); 960 emit YieldFeeRecipientSet(_yieldFeeRecipient);
file:/src/TwabERC20.sol 78 emit Transfer(address(0), _receiver, _amount); 89 emit Transfer(_owner, address(0), _amount); 102 emit Transfer(_from, _to, _amount);
file:/src/abstract/Claimable.sol 131 emit ClaimerSet(_claimer);
file:/src/abstract/HookManager.sol 31 emit SetHooks(msg.sender, hooks);
Short-circuiting is a solidity contract development model that usesĀ OR/ANDĀ logic to sequence different cost operations. It puts low gas cost operations in the front and high gas cost operations in the back, so that if the front is low If the cost operation is feasible, you can skip (short-circuit) the subsequent high-cost Ethereum virtual machine operation.
file:/src/PrizeVault.sol 776 if (success && encodedDecimals.length >= 32) {
Instead of usingĀ address(this), it is more gas-efficient to pre-calculate and use the hardcodedĀ address. Foundryās script.sol and solmateāsĀ LibRlp.solĀ contracts can help achieve this.
file:/src/PrizeVault.sol 337 return yieldVault.convertToAssets(yieldVault.balanceOf(address(this))) + _asset.balanceOf(address (this)); 382 uint256 _latentBalance = _asset.balanceOf(address(this)); 383 uint256 _maxYieldVaultDeposit = yieldVault.maxDeposit(address(this)); 405 uint256 _maxWithdraw = _maxYieldVaultWithdraw() + _asset.balanceOf(address(this)); 416 uint256 _maxWithdraw = _maxYieldVaultWithdraw() + _asset.balanceOf(address(this)); 540 IERC20Permit(address(_asset)).permit(_owner, address(this), _assets, _deadline, _v, _r, _s); 558 if (twabController.delegateOf(address(this), _owner) != SPONSORSHIP_ADDRESS) { 639 _maxAmountOut = _maxYieldVaultWithdraw() + _asset.balanceOf(address(this)); 726 return (_tokenOut == address(_asset) || _tokenOut == address(this)) && _liquidationPair == liquidationPair; 861 uint256 _assetsWithDust = _asset.balanceOf(address(this)); 866 uint256 _assetsUsed = yieldVault.mint(_yieldVaultShares, address(this)); 922 return yieldVault.convertToAssets(yieldVault.maxRedeem(address(this))); 936 yieldVault.redeem(_yieldVaultShares, address(this), address(this));
file:/src/TwabERC20.sol 59 return twabController.balanceOf(address(this), _account); 64 return twabController.totalSupply(address(this));
When we say "use mappings instead of arrays," we mean that when storing and accessing data in a smart contract, it's more gas-efficient to use mappings instead of arrays. This is because mappings do not require iteration to find a specific element, whereas arrays do.
file:/src/PrizeVaultFactory.so 66 PrizeVault[] public allVaults;
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.
file:/src/PrizeVault.sol 416 uint256 _maxWithdraw = _maxYieldVaultWithdraw() + _asset.balanceOf(address(this)); 617 yieldFeeBalance -= _yieldFeeBalance; 639 _maxAmountOut = _maxYieldVaultWithdraw() + _asset.balanceOf(address(this));
Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn't possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block.
file:/src/PrizeVault.sol 617 yieldFeeBalance -= _yieldFeeBalance; 647 uint256 _liquidYield = 648 _availableYieldBalance(totalAssets(), _totalDebt(_totalSupply)) 649 .mulDiv(FEE_PRECISION - yieldFeePercentage, FEE_PRECISION); 675 _yieldFee = (_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut; 791 return _totalSupply + yieldFeeBalance;
If the arguments to the encode call can fit into the scratch space (two words or fewer), then it's more efficient to use assembly to generate the hash (80 gas)
File: /src/PrizeVaultFactory.sol 103 salt: keccak256(abi.encode(msg.sender, deployerNonces[msg.sender]++))
file:
We can use assembly to efficiently validate msg.sender for the didPay and uniswapV3SwapCallback functions with the least amount of opcodes necessary. Additionally, we can use xor() instead of iszero(eq()), saving 3 gas. We can also potentially save gas on the unhappy path by using scratch space to store the error selector, potentially avoiding memory expansion costs.
file:/src/abstract/Claimable.sol 53 if (msg.sender != claimer) revert CallerNotClaimer(msg.sender, claimer);
#0 - raymondfam
2024-03-13T04:11:23Z
15 generic G
#1 - c4-pre-sort
2024-03-13T04:11:28Z
raymondfam marked the issue as sufficient quality report
#2 - c4-judge
2024-03-18T02:55:34Z
hansfriese marked the issue as grade-a
#3 - c4-sponsor
2024-03-18T15:13:08Z
trmid (sponsor) confirmed
#4 - c4-judge
2024-03-21T04:43:54Z
hansfriese marked the issue as grade-b