Platform: Code4rena
Start Date: 05/05/2022
Pot Size: $125,000 DAI
Total HM: 17
Participants: 62
Period: 14 days
Judge: leastwood
Total Solo HM: 15
Id: 120
League: ETH
Rank: 31/62
Findings: 2
Award: $271.81
π Selected for report: 0
π Solo Findings: 0
π Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xkatana, 0xsomeone, AuditsAreUS, BouSalman, BowTiedWardens, Cityscape, Funen, GimelSec, Hawkeye, JC, MaratCerby, MiloTruck, Picodes, Ruhum, TerrierLover, WatchPug, Waze, bobirichman, catchup, cccz, cryptphi, csanuragjain, delfin454000, ellahi, fatherOfBlocks, hake, horsefacts, hyh, jayjonah8, joestakey, kebabsec, kenta, mics, oyc_109, robee, samruna, shenwilly, sikorico, simon135, throttle, tintin
179.9731 DAI - $179.97
Contracts implementing access control's, e.g. owner
, should consider implementing a Two-Step Transfer pattern.
Otherwise it's possible that the role mistakenly transfers ownership to the wrong address, resulting in a loss of the role
function transferOwnership(address _owner) external onlyOwner { owner = _owner; }
Consider adding a pendingOwner
where the new owner will have to accept the ownership.
address owner; address pendingOwner; // ... function setPendingOwner(address newPendingOwner) external { require(msg.sender == owner, "!owner"); emit NewPendingOwner(newPendingOwner); pendingOwner = newPendingOwner; } function acceptOwnership() external { require(msg.sender == pendingOwner, "!pendingOwner"); emit NewOwner(pendingOwner); owner = pendingOwner; pendingOwner = address(0); }
address(0)
when assigning values to address state variables.Setters of address type parameters should include a zero-address check otherwise contract functionality may become inaccessible or tokens burnt forever.
function transferOwnership(address _owner) external onlyOwner { // @audit make it two-step owner = _owner; }
Add address(0)
checks.
The usage of deprecated library functions, safeApprove
& _setupRole
in this case, should be discouraged.
AlchemicTokenV1.sol::43 => _setupRole(ADMIN_ROLE, msg.sender); AlchemicTokenV1.sol::44 => _setupRole(SENTINEL_ROLE, msg.sender); AlchemicTokenV1.sol::102 => _setupRole(SENTINEL_ROLE, sentinel); AlchemicTokenV2.sol::56 => _setupRole(ADMIN_ROLE, msg.sender); AlchemicTokenV2.sol::57 => _setupRole(SENTINEL_ROLE, msg.sender); AlchemicTokenV2.sol::129 => _setupRole(SENTINEL_ROLE, sentinel); AlchemicTokenV2Base.sol::63 => _setupRole(ADMIN_ROLE, msg.sender); AlchemicTokenV2Base.sol::64 => _setupRole(SENTINEL_ROLE, msg.sender); AlchemicTokenV2Base.sol::142 => _setupRole(SENTINEL_ROLE, sentinel); AlchemistV2.sol::382 => TokenUtils.safeApprove(yieldToken, config.adapter, type(uint256).max); AlchemistV2.sol::383 => TokenUtils.safeApprove(adapter.underlyingToken(), config.adapter, type(uint256).max); AlchemistV2.sol::478 => TokenUtils.safeApprove(yieldToken, adapter, type(uint256).max); AlchemistV2.sol::479 => TokenUtils.safeApprove(ITokenAdapter(adapter).underlyingToken(), adapter, type(uint256).max); EthAssetManager.sol::576 => SafeERC20.safeApprove(address(tokens[i]), address(metaPool), 0); EthAssetManager.sol::577 => SafeERC20.safeApprove(address(tokens[i]), address(metaPool), amounts[i]); EthAssetManager.sol::620 => SafeERC20.safeApprove(address(token), address(metaPool), 0); EthAssetManager.sol::621 => SafeERC20.safeApprove(address(token), address(metaPool), amount); EthAssetManager.sol::671 => SafeERC20.safeApprove(address(metaPool), address(convexBooster), 0); EthAssetManager.sol::672 => SafeERC20.safeApprove(address(metaPool), address(convexBooster), amount); ThreePoolAssetManager.sol::782 => SafeERC20.safeApprove(address(tokens[i]), address(threePool), 0); ThreePoolAssetManager.sol::783 => SafeERC20.safeApprove(address(tokens[i]), address(threePool), amounts[i]); ThreePoolAssetManager.sol::838 => SafeERC20.safeApprove(address(token), address(threePool), 0); ThreePoolAssetManager.sol::839 => SafeERC20.safeApprove(address(token), address(threePool), amount); ThreePoolAssetManager.sol::879 => SafeERC20.safeApprove(address(threePoolToken), address(threePool), 0); ThreePoolAssetManager.sol::880 => SafeERC20.safeApprove(address(threePoolToken), address(threePool), amount); ThreePoolAssetManager.sol::908 => SafeERC20.safeApprove(address(tokens[i]), address(metaPool), 0); ThreePoolAssetManager.sol::909 => SafeERC20.safeApprove(address(tokens[i]), address(metaPool), amounts[i]); ThreePoolAssetManager.sol::944 => SafeERC20.safeApprove(address(token), address(metaPool), 0); ThreePoolAssetManager.sol::945 => SafeERC20.safeApprove(address(token), address(metaPool), amount); ThreePoolAssetManager.sol::987 => SafeERC20.safeApprove(address(metaPool), address(convexBooster), 0); ThreePoolAssetManager.sol::988 => SafeERC20.safeApprove(address(metaPool), address(convexBooster), amount); TransmuterBuffer.sol::85 => _setupRole(ADMIN, _admin); TransmuterBuffer.sol::236 => TokenUtils.safeApprove(registeredUnderlyings[i], alchemist, 0); TransmuterBuffer.sol::238 => TokenUtils.safeApprove(debtToken, alchemist, 0); TransmuterBuffer.sol::243 => TokenUtils.safeApprove(registeredUnderlyings[i], alchemist, type(uint256).max); TransmuterBuffer.sol::245 => TokenUtils.safeApprove(debtToken, alchemist, type(uint256).max); TransmuterBuffer.sol::284 => TokenUtils.safeApprove(underlyingToken, alchemist, type(uint256).max); TransmuterV2.sol::149 => _setupRole(ADMIN, msg.sender); adapters/fuse/FuseTokenAdapterV1.sol::71 => SafeERC20.safeApprove(underlyingToken, token, amount); adapters/lido/WstETHAdapterV1.sol::105 => SafeERC20.safeApprove(parentToken, address(token), mintedStEth); adapters/lido/WstETHAdapterV1.sol::129 => SafeERC20.safeApprove(parentToken, curvePool, unwrappedStEth); adapters/vesper/VesperAdapterV1.sol::62 => SafeERC20.safeApprove(underlyingToken, token, amount); adapters/yearn/YearnTokenAdapter.sol::32 => TokenUtils.safeApprove(underlyingToken, token, amount);
_setupRole
function is deprecated in favor of _grantRole
. Use safeIncreaseAllowance
/ safeDecreaseAllowance
instead of safeApprove
.
The return value of an external transfer
/transferFrom
call is not checked
AutoleverageCurveFactoryethpool.sol::26 => IERC20(underlyingToken).transferFrom(msg.sender, address(this), collateralInitial); AutoleverageCurveMetapool.sol::16 => IERC20(underlyingToken).transferFrom(msg.sender, address(this), collateralInitial);
Use SafeERC20
, or ensure that the transfer
/transferFrom
return value is checked.
/// @dev A modifier which checks if caller is a sentinel. modifier notPaused() { if (isPaused) { revert IllegalState(); } _; }
The modifier does not check if caller is a sentinel. It checks if the function is paused or not.
uint
as uint256
.To favor explicitness, all instances of uint
should be declared as uint256
.
manual, slither
#0 - 0xfoobar
2022-05-30T08:06:35Z
Great QA and recommendations
π Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xf15ers, 0xkatana, 0xsomeone, AlleyCat, BowTiedWardens, Cityscape, Fitraldys, Funen, GimelSec, Hawkeye, JC, MaratCerby, MiloTruck, Randyyy, TerrierLover, Tomio, UnusualTurtle, WatchPug, Waze, _Adam, augustg, bobirichman, catchup, csanuragjain, ellahi, fatherOfBlocks, hake, hansfriese, horsefacts, ignacio, joestakey, kenta, mics, oyc_109, robee, samruna, sashik_eth, sikorico, simon135, throttle
91.8428 DAI - $91.84
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack. Caching the array length in the stack saves around 3 gas per iteration.
AlchemistV2.sol::990 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1282 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1355 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1461 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1524 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { CrossChainCanonicalBase.sol::57 => for (uint256 i = 0; i < _bridgeTokens.length; i++){ CrossChainCanonicalBase.sol::141 => for (uint i = 0; i < bridgeTokensArray.length; i++){ StakingPools.sol::188 => for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) { StakingPools.sol::363 => for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) { TransmuterBuffer.sol::172 => for (uint256 i = 0; i < _yieldTokens[underlyingToken].length; i++) { TransmuterBuffer.sol::186 => for (uint256 i = 0; i < tokens.length; i++) { TransmuterBuffer.sol::235 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::242 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::272 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::382 => for (uint256 j = 0; j < registeredUnderlyings.length; j++) { TransmuterBuffer.sol::479 => for (uint256 j = 0; j < weighting.tokens.length; j++) { base/Multicall.sol::14 => for (uint256 i = 0; i < data.length; i++) {
Store the arrayβs length in a variable before the for-loop.
Use != 0
instead of > 0
.
If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
AlchemistV2.sol::990 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1282 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1355 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1458 => uint256 totalValue = 0; AlchemistV2.sol::1461 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1524 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { CrossChainCanonicalBase.sol::57 => for (uint256 i = 0; i < _bridgeTokens.length; i++){ CrossChainCanonicalBase.sol::141 => for (uint i = 0; i < bridgeTokensArray.length; i++){ EthAssetManager.sol::214 => for (uint256 i = 0; i < NUM_META_COINS; i++) { EthAssetManager.sol::566 => uint256 total = 0; EthAssetManager.sol::567 => for (uint256 i = 0; i < NUM_META_COINS; i++) { ThreePoolAssetManager.sol::250 => for (uint256 i = 0; i < NUM_STABLE_COINS; i++) { ThreePoolAssetManager.sol::254 => for (uint256 i = 0; i < NUM_META_COINS; i++) { ThreePoolAssetManager.sol::353 => for (uint256 i = 0; i < 256; i++) { ThreePoolAssetManager.sol::773 => for (uint256 i = 0; i < NUM_STABLE_COINS; i++) { ThreePoolAssetManager.sol::901 => uint256 total = 0; ThreePoolAssetManager.sol::902 => for (uint256 i = 0; i < NUM_META_COINS; i++) { TransmuterBuffer.sol::172 => for (uint256 i = 0; i < _yieldTokens[underlyingToken].length; i++) { TransmuterBuffer.sol::186 => for (uint256 i = 0; i < tokens.length; i++) { TransmuterBuffer.sol::235 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::242 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::272 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::382 => for (uint256 j = 0; j < registeredUnderlyings.length; j++) { TransmuterBuffer.sol::387 => for (uint256 i = 0; i < numYTokens; i++) { TransmuterBuffer.sol::479 => for (uint256 j = 0; j < weighting.tokens.length; j++) { TransmuterBuffer.sol::534 => uint256 want = 0; TransmuterBuffer.sol::549 => uint256 exchangeDelta = 0; base/Multicall.sol::14 => for (uint256 i = 0; i < data.length; i++) {
Remove explicit zero initialization.
++i
costs less gas compared to i++
or i += 1
++i
costs less gas compared to i++
or i += 1
for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.
AlchemistV2.sol::990 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1282 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1355 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1461 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { AlchemistV2.sol::1524 => for (uint256 i = 0; i < depositedTokens.values.length; i++) { CrossChainCanonicalBase.sol::57 => for (uint256 i = 0; i < _bridgeTokens.length; i++){ CrossChainCanonicalBase.sol::141 => for (uint i = 0; i < bridgeTokensArray.length; i++){ StakingPools.sol::188 => for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) { StakingPools.sol::363 => for (uint256 _poolId = 0; _poolId < _pools.length(); _poolId++) { TransmuterBuffer.sol::172 => for (uint256 i = 0; i < _yieldTokens[underlyingToken].length; i++) { TransmuterBuffer.sol::186 => for (uint256 i = 0; i < tokens.length; i++) { TransmuterBuffer.sol::235 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::242 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::272 => for (uint256 i = 0; i < registeredUnderlyings.length; i++) { TransmuterBuffer.sol::382 => for (uint256 j = 0; j < registeredUnderlyings.length; j++) { TransmuterBuffer.sol::479 => for (uint256 j = 0; j < weighting.tokens.length; j++) { base/Multicall.sol::14 => for (uint256 i = 0; i < data.length; i++) {
Use ++i
instead of i++
to increment the value of an uint variable.
Same thing for --i
and i--
.
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met. Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
AlchemicTokenV1.sol::51 => require(whiteList[msg.sender], "AlTokenV1: Alchemist is not whitelisted"); AlchemicTokenV1.sol::81 => require(total <= ceiling[msg.sender], "AlUSD: Alchemist's ceiling was breached."); StakingPools.sol::106 => require(_governance != address(0), "StakingPools: governance address cannot be 0x0"); StakingPools.sol::124 => require(_pendingGovernance != address(0), "StakingPools: pending governance address cannot be 0x0"); StakingPools.sol::131 => require(msg.sender == pendingGovernance, "StakingPools: only pending governance"); StakingPools.sol::160 => require(tokenPoolIds[_token] == 0, "StakingPools: token already has a pool"); StakingPools.sol::183 => require(_rewardWeights.length == _pools.length(), "StakingPools: weights length mismatch");
Shorten the revert strings to fit in 32 bytes, or use custom errors if >0.8.4.
Waste of gas. Check @audit
.
// @audit get rid of exchangedBalance function _getExchangedBalance(address owner) internal view returns (uint256 exchangedBalance) { Account storage account = accounts[owner]; // @audit add uint256 exchangedBalance; here if (account.occupiedTick <= satisfiedTick) { exchangedBalance = account.exchangedBalance; exchangedBalance += account.unexchangedBalance; return exchangedBalance; } exchangedBalance = account.exchangedBalance; uint256 exchanged = LiquidityMath.calculateProduct( account.unexchangedBalance, ticks.getWeight(account.occupiedTick, ticks.position) ); exchangedBalance += exchanged; return exchangedBalance; }
Get rid of named return and initialize variable in function. Saves a bit of gas.
c4udit, manual, slither