Platform: Code4rena
Start Date: 14/09/2022
Pot Size: $50,000 USDC
Total HM: 25
Participants: 110
Period: 5 days
Judge: hickuphh3
Total Solo HM: 9
Id: 162
League: ETH
Rank: 69/110
Findings: 1
Award: $52.83
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: pfapostol
Also found by: 0x040, 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, 0xc0ffEE, 0xkatana, Aymen0909, Bnke0x0, Deivitto, Diana, JAGADESH, KIntern_NA, Lambda, MiloTruck, R2, RaymondFam, Respx, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, Ruhum, Saintcode_, Samatak, Sm4rty, SnowMan, Tomio, Tomo, WilliamAmbrozic, _Adam, __141345__, ajtra, ak1, async, c3phas, ch0bu, cryptostellar5, d3e4, delfin454000, dharma09, djxploit, durianSausage, eierina, erictee, fatherOfBlocks, gianganhnguyen, gogo, ignacio, imare, jag, jonatascm, leosathya, lukris02, malinariy, oyc_109, pashov, pauliax, peanuts, peiw, prasantgupta52, robee, rokinot, rotcivegaf, rvierdiiev, seyni, simon135, slowmoses, sryysryy, tnevler, zishansami
52.8286 USDC - $52.83
The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
// Links to github files Vault.sol:L443
src/Vault.sol:443: for (uint256 i = 0; i < epochsLength(); i++) {
Pre-increments and pre-decrements are cheaper.
For a uint256
i variable, the following is true with the Optimizer enabled at 10k:
Increment:
i += 1
is the most expensive form
i++
costs 6
gas
less than i += 1
++i
costs 5 gas
less than i++
(11 gas less than i += 1)
Note that post-increments (or post-decrements) return the old value before incrementing or decrementing, hence the name post-increment:
// Links to Github file Vault.sol:L443 VaultFactory.sol:L195
src/Vault.sol:443: for (uint256 i = 0; i < epochsLength(); i++) { src/VaultFactory.sol:195: marketIndex += 1;
Using > 0
uses slightly more gas than using != 0
.
Use != 0
when comparing uint variables to zero, which cannot hold values below zero
When dealing with unsigned integer types, comparisons with != 0 are cheaper then with > 0. This change saves 6 gas per instance
Incidentally, after version 0.8.12, this optimization doesn’t work
You should change from > 0
to !=0
.
// Links to Github file Vault.sol:L187 PegOracle.sol:L98 PegOracle.sol:L121 StakingRewards.sol:L119
src/Vault.sol:187: require(msg.value > 0, "ZeroValue"); src/oracles/PegOracle.sol:98: require(price1 > 0, "Chainlink price <= 0"); src/oracles/PegOracle.sol:121: require(price2 > 0, "Chainlink price <= 0"); src/rewards/StakingRewards.sol:119: require(amount > 0, "Cannot withdraw 0");
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
We can use uint number;
instead of uint number = 0;
// Links to github files StakingRewards.sol:L36
src/rewards/StakingRewards.sol:36: uint256 public periodFinish = 0;
I suggest removing explicit initializations for default values.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Source Custom Errors in Solidity:
Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error
statement, which can be used inside and outside of contracts (including interfaces and libraries).
// Links to github files Vault.sol:L165 Vault.sol:L187 PegOracle.sol:L23 PegOracle.sol:L24 PegOracle.sol:L25 PegOracle.sol:L98 PegOracle.sol:L103 PegOracle.sol:L121 PegOracle.sol:L126 SemiFungibleVault.sol:L91 StakingRewards.sol:L96 StakingRewards.sol:L119
src/Vault.sol:165: require((shares = previewDeposit(id, assets)) != 0, "ZeroValue"); src/Vault.sol:187: require(msg.value > 0, "ZeroValue"); src/oracles/PegOracle.sol:23: require(_oracle1 != address(0), "oracle1 cannot be the zero address"); src/oracles/PegOracle.sol:24: require(_oracle2 != address(0), "oracle2 cannot be the zero address"); src/oracles/PegOracle.sol:25: require(_oracle1 != _oracle2, "Cannot be same Oracle"); src/oracles/PegOracle.sol:98: require(price1 > 0, "Chainlink price <= 0"); src/oracles/PegOracle.sol:103: require(timeStamp1 != 0, "Timestamp == 0 !"); src/oracles/PegOracle.sol:121: require(price2 > 0, "Chainlink price <= 0"); src/oracles/PegOracle.sol:126: require(timeStamp2 != 0, "Timestamp == 0 !"); src/SemiFungibleVault.sol:91: require((shares = previewDeposit(id, assets)) != 0, "ZERO_SHARES"); src/rewards/StakingRewards.sol:96: require(amount != 0, "Cannot stake 0"); src/rewards/StakingRewards.sol:119: require(amount > 0, "Cannot withdraw 0");
replace revert strings with custom errors.
abi.encodePacked()
not abi.encode()
Changing abi.encode
to abi.encodePacked
can save gas. abi.encode
pads extra null bytes at the end of the call data which is normally unnecessary. In general, abi.encodePacked
is more gas-efficient.
// Links to github files RewardsFactory.sol:L118 RewardsFactory.sol:L150 RewardsFactory.sol:L118 RewardsFactory.sol:L150
src/rewards/RewardsFactory.sol:118: bytes32 hashedIndex = keccak256(abi.encode(_marketIndex, _epochEnd)); src/rewards/RewardsFactory.sol:150: return keccak256(abi.encode(_index, _epoch)); src/rewards/RewardsFactory.sol:118: bytes32 hashedIndex = keccak256(abi.encode(_marketIndex, _epochEnd)); src/rewards/RewardsFactory.sol:150: return keccak256(abi.encode(_index, _epoch));
You should change abi.encode to abi.encodePacked
Comparing to a constant (true or false) is a bit more expensive than directly checking the returned boolean value. I suggest using if(directValue) instead of if(directValue == true) and if(!directValue) instead of if(directValue == false) here
// Links to github files Vault.sol:L314 Controller.sol:L93 Controller.sol:L211 Vault.sol:L96 RewardsFactory.sol:L96
src/Vault.sol:314: if(idExists[epochEnd] == true) src/Controller.sol:93: if(vault.idExists(epochEnd) == false) src/Controller.sol:211: if(insrVault.idExists(epochEnd) == false || riskVault.idExists(epochEnd) == false) src/Vault.sol:96: if((block.timestamp < id) && idDepegged[id] == false) src/rewards/RewardsFactory.sol:96: if(Vault(_insrToken).idExists(_epochEnd) == false || Vault(_riskToken).idExists(_epochEnd) == false)