Platform: Code4rena
Start Date: 21/04/2022
Pot Size: $100,000 USDC
Total HM: 18
Participants: 60
Period: 7 days
Judge: gzeon
Total Solo HM: 10
Id: 112
League: ETH
Rank: 30/60
Findings: 2
Award: $314.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x52, 0xDjango, 0xkatana, Dravee, Funen, Kenshin, Ruhum, StyxRave, Tadashi, TerrierLover, TrungOre, antonttc, berndartmueller, catchup, csanuragjain, defsec, dipp, fatherOfBlocks, hake, horsefacts, hubble, jayjonah8, joestakey, kebabsec, kenta, m4rio_eth, oyc_109, pauliax, peritoflores, rayn, remora, robee, securerodd, simon135, sorrynotsorry, sseefried, z3s
169.5152 USDC - $169.52
The approve(address, uint256)
function in StakerVault.sol
does not validate that the previous allowance was already used up before updating the allowance. If a user uses the approve function instead of decreaseAllowance to lower their allowance, they would be subject to a double withdrawal attack (through frontrunning).
Scenario:
If User A has an allowance of 10 stake tokens for User B and they use the `approve(address spender, uint256 amount)` function to change the amount to 5 stake tokens, user B could frontrun that approve transaction to perform a `transferFrom(address src, address dst, uint256 amount)` while the allowance is still 10, setting the allowance to 0. Now when user A’s transaction is processed, this reupdates the allowance to 5. User B could now call the `transferFrom(address src, address dst, uint256 amount)` function once more for an additional 5 stake tokens bringing the the total stake tokens transferred to user B to 15.
Recommendation: Add functions to atomically increase and decrease the _allowances mapping.
Contract: LPToken.sol
The naming convention for the onlyMinter
modifier creates some degree of ambiguity as minter roles for ERC-20 tokens are frequently held by EOAs. In this case, the minter is actually a liquidity pool and this is a key distinction as the onlyMinter
role has the ability to burn any user's tokens. The modifier would read more clearly if it were, for example onlyPool
as opposed to onlyMinter
.
Contract: StakerVault.sol
Code:
/** * @notice This contract handles staked tokens from Backd pools * However, not that this is NOT an ERC-20 compliant contract and these //@audit 'not' should be 'note' * tokens should never be integrated with any protocol assuming ERC-20 compliant * tokens * @dev When paused, allows only withdraw/unstake */
Contract: StakerVault.sol
Code:
/** * @notice Burn tokens. * @param owner Account from which tokens should be burned. * @param burnAmount Amount of tokens to burn. * @return Aamount of tokens burned. //@audit 'Aamount' should be 'Amount' */
🌟 Selected for report: joestakey
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xkatana, 0xmint, Dravee, Funen, IllIllI, MaratCerby, NoamYakov, Tadashi, TerrierLover, Tomio, WatchPug, catchup, defsec, fatherOfBlocks, hake, horsefacts, kenta, oyc_109, pauliax, rayn, rfa, robee, saian, securerodd, simon135, slywaters, sorrynotsorry, tin537, z3s
145.2735 USDC - $145.27
The for loops used throughout the codebase can benefit from one or more of the following gas saving techniques:
1. Storing the array length in a local variable in memory before comparisons 2. Using a prefix increment instead of postfix 3. Performing unchecked arithmetic on the counter variable 4. Not initializing the counter to 0
Current code in RoleManager.sol
:
function hasAnyRole(bytes32[] memory roles, address account) external view virtual override returns (bool) { for (uint256 i = 0; i < roles.length; i++) { if (hasRole(roles[i], account)) { return true; } } return false; }
Suggested code:
function hasAnyRole(bytes32[] memory roles, address account) external view virtual override returns (bool) { uint256 roleLength = roles.length; for (uint256 i; i < roleLength;) { if (hasRole(roles[i], account)) { return true; } unchecked { ++i; } } return false; }
The following locations could also benefit from some or all of the above techniques:
Nested if and require statements are more gas efficient than in-line conditions using the && operator. There were several locations in the codebase that could benefit from using nested statements instead.
Current code in ConvexStrategyBase.sol
:
function addRewardToken(address token_) external onlyGovernance returns (bool) { require( token_ != address(_CVX) && token_ != address(underlying) && token_ != address(_CRV), Error.INVALID_TOKEN_TO_ADD );
Suggested code:
function addRewardToken(address token_) external onlyGovernance returns (bool) { require(token_ != address(_CVX)); require(token_ != address(underlying)); require(token_ != address(_CRV));
The following locations could also benefit from the above technique:
within require statements, != comparisons are slightly more gas efficient than > and when a uint is being compared to 0, != is effectively the same as > 0.
The following locations could benefit from this technique:
When initalizing a variable, setting its value to 0 explicitly is slightly less efficient than not setting its value.
The following locations could benefit from this technique:
#0 - chase-manning
2022-04-28T09:47:52Z
I consider this report to be of particularly high quality