Platform: Code4rena
Start Date: 31/01/2023
Pot Size: $90,500 USDC
Total HM: 47
Participants: 169
Period: 7 days
Judge: LSDan
Total Solo HM: 9
Id: 211
League: ETH
Rank: 6/169
Findings: 6
Award: $2,246.26
π Selected for report: 2
π Solo Findings: 1
π Selected for report: 0xdeadbeef0x
Also found by: 0Kage, 0xNazgul, 0xRobocop, Aymen0909, KIntern_NA, Kenshin, KingNFT, Krace, Kumpa, SadBase, aashar, apvlki, btk, cccz, critical-or-high, eccentricexit, fs0c, gjaldon, hansfriese, immeas, mert_eren, mgf15, mrpathfindr, orion, peanuts, rvi0x, rvierdiiev, supernova, ulqiorra, waldenyan20, y1cunhui
4.6115 USDC - $4.61
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardStaking.sol#L170
The claimRewards()
function is used to claim rewards for a user in any amount of rewardTokens
. Any ERC20 can be added meaning, any ERC777 token can be to since they are backwards-compatible.
This introduces a Reentracy vulnerability allowing anyone to get more accruedRewards
draining the contract. Looking at The claimRewards()
function:
function claimRewards(address user, IERC20[] memory _rewardTokens) external accrueRewards(msg.sender, user) { for (uint8 i; i < _rewardTokens.length; i++) { uint256 rewardAmount = accruedRewards[user][_rewardTokens[i]]; if (rewardAmount == 0) revert ZeroRewards(_rewardTokens[i]); EscrowInfo memory escrowInfo = escrowInfos[_rewardTokens[i]]; if (escrowInfo.escrowPercentage > 0) { _lockToken(user, _rewardTokens[i], rewardAmount, escrowInfo); //@audit if rewardToken were an erc777, it can reenter draining emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, true); } else { _rewardTokens[i].transfer(user, rewardAmount); //@audit if rewardToken were an erc777, it can reenter draining emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, false); } accruedRewards[user][_rewardTokens[i]] = 0; } }
Specifically line #L186
which is the point where the users accruedRewards
is updated. Because it is done after both _lockToken()
and transfer()
Reentracy is possible with the use of ERC777.
Manual Review
Consider ensuring that no ERC777 tokens are able to be used as a reward token. Could also use a Reentrancy modifier and move line #L186
before like so:
function claimRewards(address user, IERC20[] memory _rewardTokens) external accrueRewards(msg.sender, user) { for (uint8 i; i < _rewardTokens.length; i++) { uint256 rewardAmount = accruedRewards[user][_rewardTokens[i]]; if (rewardAmount == 0) revert ZeroRewards(_rewardTokens[i]); EscrowInfo memory escrowInfo = escrowInfos[_rewardTokens[i]]; accruedRewards[user][_rewardTokens[i]] = 0; //@audit move accruedRewards here to prevent Reentrancy if (escrowInfo.escrowPercentage > 0) { _lockToken(user, _rewardTokens[i], rewardAmount, escrowInfo);//@audit if rewardToken were an erc777, it can reenter draining emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, true); } else { _rewardTokens[i].transfer(user, rewardAmount); emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, false); } } }
#0 - c4-judge
2023-02-16T07:39:43Z
dmvt marked the issue as duplicate of #54
#1 - c4-sponsor
2023-02-18T12:11:07Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T00:49:32Z
dmvt marked the issue as partial-50
#3 - c4-judge
2023-03-01T00:36:43Z
dmvt marked the issue as full credit
#4 - c4-judge
2023-03-01T00:37:29Z
dmvt marked the issue as satisfactory
π Selected for report: 0xNineDec
Also found by: 0xBeirao, 0xNazgul, 0xRajkumar, Blockian, Breeje, CRYP70, Josiah, KIntern_NA, MyFDsYours, Qeew, RaymondFam, Ruhum, UdarTeam, chaduke, giovannidisiena, gjaldon, immeas, koxuan, nadin, peanuts, rbserver, rvi0x, savi0ur
14.2839 USDC - $14.28
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/beefy/BeefyAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L88 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L154 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardStaking.sol#L26 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L26
Vault tokens can be stolen from depositors in all EIP4626 Implementations vaults by manipulating the price of a share.
ERC4626 vaults are subject to a share price manipulation attack that allows an attacker to steal underlying tokens from other depositors (this is a known issue of ERC4626 implementation). Consider this scenario for Vault.sol
:
convertToShares()
function:function convertToShares(uint256 assets) public view returns (uint256) { uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero. return supply == 0 ? assets : assets.mulDiv(supply, totalAssets(), Math.Rounding.Down); }
convertToShares()
function: 19e18 * 1 / 10e18 == 1
;Manual Review
deposit()
functions, consider requiring a reasonably high minimal amount of assets during first deposit. The amount needs to be high enough to mint many shares to reduce the rounding error and low enough to be affordable to users.#0 - c4-judge
2023-02-16T03:30:55Z
dmvt marked the issue as duplicate of #15
#1 - c4-sponsor
2023-02-18T11:54:49Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T00:11:01Z
dmvt marked the issue as satisfactory
678.8223 USDC - $678.82
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/VaultController.sol#L238
Across the VaultController.sol
there are many external calls to the AdminProxy.sol
via execute()
. Looking at the execute()
function in AdminProxy.sol
:
function execute(address target, bytes calldata callData) external onlyOwner returns (bool success, bytes memory returndata) { return target.call(callData); }
As one can see it does a call to the target contract with the provided callData
. Going back to the VaultController.sol
the success of the call is check and reverts if unsuccessful. However, in one instance this check is missed and could cause issues.
Looking at that specific instance:
function __deployAdapter( DeploymentArgs memory adapterData, bytes memory baseAdapterData, IDeploymentController _deploymentController ) internal returns (address adapter) { (bool success, bytes memory returnData) = adminProxy.execute( address(_deploymentController), abi.encodeWithSelector(DEPLOY_SIG, ADAPTER, adapterData.id, _encodeAdapterData(adapterData, baseAdapterData)) ); if (!success) revert UnderlyingError(returnData); adapter = abi.decode(returnData, (address)); adminProxy.execute(adapter, abi.encodeWithSelector(IAdapter.setPerformanceFee.selector, performanceFee));//@audit unchecked like the others are }
It is clear that the last call to AdminProxy.sol
's execute
is not checked.
Manual Review
Consider adding a check similar to how it is done in the rest of the contract.
#0 - c4-judge
2023-02-16T07:50:07Z
dmvt marked the issue as primary issue
#1 - c4-sponsor
2023-02-17T10:14:52Z
RedVeil marked the issue as disagree with severity
#2 - c4-sponsor
2023-02-17T10:14:58Z
RedVeil marked the issue as sponsor confirmed
#3 - c4-judge
2023-02-25T23:34:36Z
dmvt marked the issue as selected for report
π Selected for report: 0xNazgul
1508.4941 USDC - $1,508.49
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L525
Currently in Vault.sol
there are four different fee types:
There is a proper check to make sure that individually none of them are >= 1e18
. However, they can total to more than 1e18
and cause unsuspecting users to pay more than they may want to.
Just taking the deposit and withdrawal fees into account say both of them are set to 5e17
, totaling to 1e18
. If a user were to than deposit and withdraw that would be 100%. Add in the other two fees and this situation gets even worse.
Manual Review
Consider making sure that the total of all four fee types to be less than 1e18 instead of individually.
#0 - c4-sponsor
2023-02-17T13:47:11Z
RedVeil marked the issue as sponsor acknowledged
#1 - c4-judge
2023-02-25T23:33:18Z
dmvt marked the issue as selected for report
#2 - c4-judge
2023-03-01T01:08:19Z
dmvt marked the issue as primary issue
π Selected for report: csanuragjain
Also found by: 0xNazgul, 0xNineDec, 0xSmartContract, 0xdeadbeef0x, Bauer, Deivitto, Josiah, KIntern_NA, RaymondFam, Rolezn, UdarTeam, Viktor_Cortess, btk, joestakey, koxuan, pavankv, rbserver, rvi0x
4.5833 USDC - $4.58
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/beefy/BeefyAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L88 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L154 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardStaking.sol#L26 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L26
There are ERC20 tokens that may make certain customizations to their ERC20 contracts. One type of these tokens is deflationary tokens that charge a certain fee for every transfer() or transferFrom(). Others are rebasing tokens that increase in value over time like Aave's aTokens (balanceOf changes over time).
Across a multiple different functions in all of the contracts will will store the entire amount but with fee-on-transfer tokens, fewer tokens will be transferred which leads to inconsistencies.
Manual Review
Consider checking actual balance of the contract or ensure that the protocol never uses rebasing or tokens with fee-on transfer.
#0 - c4-judge
2023-02-16T07:07:26Z
dmvt marked the issue as duplicate of #44
#1 - c4-sponsor
2023-02-18T11:48:57Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T00:45:30Z
dmvt marked the issue as partial-50