Platform: Code4rena
Start Date: 15/06/2022
Pot Size: $35,000 USDC
Total HM: 1
Participants: 36
Period: 3 days
Judge: Jack the Pug
Total Solo HM: 1
Id: 137
League: ETH
Rank: 8/36
Findings: 2
Award: $164.96
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xNazgul
Also found by: 0xDjango, 0xFar5eer, 0xf15ers, BowTiedWardens, Chom, Dravee, IllIllI, Meera, MiloTruck, PierrickGT, TerrierLover, _Adam, cccz, codexploder, cryptphi, delfin454000, fatherOfBlocks, hansfriese, joestakey, oyc_109, simon135
81.8216 USDC - $81.82
In the deposit
function on line 48, we call ExchangeHelpers.setMaxAllowance(token, vault);
to allow the vault
to spend token
.
Each time assets are deposited in the vault, we shouldn't have to allow it to spend the token again.
I recommend to call ExchangeHelpers.setMaxAllowance(token, vault)
only once in the constructor for each vault and token. I also recommend to add a setMaxAllowance
function only callable by the owner of the operator that would allow to set the max allowance in case the allowance has decreased.
Recommendation:
for (uint256 i; i < vaultsLength; i++) { operatorStorage.addVault(vaults[i], tokens[i]); ExchangeHelpers.setMaxAllowance(tokens[i], vaults[i]); }
In the _zapAndStakeLp
function on line 189 and subsequently on line 194 and 195 we call ExchangeHelpers.setMaxAllowance();
to allow the vault
to spend token
.
Each time assets are deposited in the vault, we shouldn't have to allow it to spend the token again.
I recommend to call ExchangeHelpers.setMaxAllowance()
only once in the constructor for each vault and token. I also recommend to add a setMaxAllowance
function only callable by the owner of the operator that would allow to set the max allowance in case the allowance has decreased.
This will also avoid calling ExchangeHelpers.setMaxAllowance(IERC20(swapToken), router);
in the _withdrawAndSwap
function on line 162.
Recommendation:
for (uint256 i; i < vaultsLength; i++) { operatorStorage.addVault(vaults[i], routers[i]); IBiswapPair pair = IBiswapPair(IBeefyVaultV6(vaults[i]).want()); ExchangeHelpers.setMaxAllowance(IERC20(address(pair)), address(vaults[i])); ExchangeHelpers.setMaxAllowance(IERC20(pair.token0()), routers[i]); ExchangeHelpers.setMaxAllowance(IERC20(pair.token1()), routers[i]); }
In the depositETH
function on line 78, we call ExchangeHelpers.setMaxAllowance(IERC20(address(weth)), address(withdrawer));
to allow the withdrawer
to spend weth
.
Each time assets are deposited in the vault, we shouldn't have to allow the withdrawer to spend weth again.
I recommend to call ExchangeHelpers.setMaxAllowance(IERC20(address(weth)), address(withdrawer))
only once in the constructor. I also recommend to add a setMaxAllowance
function only callable by the owner of the operator that would allow to set the max allowance in case the allowance has decreased.
Recommendation:
ExchangeHelpers.setMaxAllowance(IERC20(_weth), address(_withdrawer));
In the addLiquidityAndDepositETH
function on line 45, we call ExchangeHelpers.setMaxAllowance(lpToken, vault);
to allow the vault
to spend lpToken
.
Each time assets are deposited in the vault, we shouldn't have to allow the vault to spend lpToken again.
I recommend to call ExchangeHelpers.setMaxAllowance(lpToken, vault);
only once in the addVault function of the YearnVaultStorage. I also recommend to add a setMaxAllowance
function only callable by the owner that would allow to set the max allowance in case the allowance has decreased.
This will also avoid calling it in the _addLiquidityAndDeposit
function on line 77
Recommendation:
ExchangeHelpers.setMaxAllowance(curvePool.lpToken, vault);
#0 - Yashiru
2022-06-23T09:28:17Z
As it is the factory that has the approval in this context, we cannot be sure that no other operator will change the allocation. So we prefer call the setMaxAllowance in final process to be sure that the factory has the allowance.
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xKitsune, 0xNazgul, 0xkatana, Chom, ElKu, JC, Meera, MiloTruck, Picodes, PierrickGT, SooYa, TerrierLover, UnusualTurtle, Waze, _Adam, asutorufos, c3phas, delfin454000, fatherOfBlocks, joestakey, minhquanym, oyc_109, robee, sach1r0, simon135
83.1436 USDC - $83.14
In the _transferToReserveAndStore
function, we store the reserve balance after the transfer in the balanceReserveAfter
variable, on line 523.
This variable being only used once, we can inline it and save one mstore.
Recommendation:
nestedRecords.store(_nftId, address(_token), _token.balanceOf(reserveAddr) - balanceReserveBefore, reserveAddr);
In the scheduleBatch
function on line 221, we call targets.length
to compare the targets length and also to loop through the targets and emit the CallScheduled
event.
For a better code legibility and also to save some mload, targets length should be stored in a variable.
Recommendation:
uint256 _targetsLength = targets.length; require(targetsLength == values.length, "TimelockController: length mismatch"); require(targetsLength == datas.length, "TimelockController: length mismatch"); bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt); schedule(id, delay); for (uint256 i = 0; i < targetsLength; ++i) { emit CallScheduled(id, i, targets[i], values[i], datas[i], predecessor, delay); }
In the executeBatch
function on line 312, we call targets.length
to compare the targets length and also to loop through the targets and call the _call
function.
For a better code legibility and also to save some mload, targets length should be stored in a variable.
Recommendation:
uint256 _targetsLength = targets.length; require(_targetsLength == values.length, "TimelockController: length mismatch"); require(_targetsLength == datas.length, "TimelockController: length mismatch"); bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt); _beforeCall(id, predecessor); for (uint256 i = 0; i < _targetsLength; ++i) { _call(id, i, targets[i], values[i], datas[i]); } _afterCall(id);
#0 - Yashiru
2022-06-24T09:05:57Z
Gas optimization confirmed
#1 - Yashiru
2022-06-24T11:38:42Z
Gas optimization confirmed
Gas optimization confirmed