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: 29/169
Findings: 3
Award: $892.68
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: bin2chen
Also found by: 0xTraub, Ch_301, rvierdiiev
704.9309 USDC - $704.93
https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/beefy/BeefyAdapter.sol#L80
Vault creator can initialize a BeefyAdapter with a booster address that allows them to rug the entirety of the BeefyAdapter. The system checks that the template being deployed is endorsed, but does not check the validity of the parameters it is being initialized with. When deploying a BeefyAdapter the user has the option to initialize with a Booster Address for harvesting additional rewards. There is no validation to ensure that this is a legitimate Booster contract with Beefy, and it is given an unlimited allowance to transfer the Moo tokens held by the adapter. It only requires that certain functions exist so that a deposit can succeed but that's it. A user could deploy the existing endorsed-template and initialize with a booster contract controlled by them. This contract could appear legitimate by proxying all of the booster functionality to accrue value to the adapter. Then they would simply activate the functionality that transfers away all of the Moo tokens to an external address owned by them, at which the vault is now insolvent.
https://gist.github.com/jhweintraub/8685c451e0113af8651cb56b295a0ca8
Foundry
Include input validation on various external contracts designated at initialization. Consider using a permission registry of allowed Booster contracts and ensure that the provided one is whitelisted/not_blacklisted.
#0 - c4-judge
2023-02-16T08:13:38Z
dmvt marked the issue as primary issue
#1 - c4-sponsor
2023-02-17T11:09:25Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T01:19:26Z
dmvt marked the issue as unsatisfactory: Insufficient quality
#3 - c4-judge
2023-02-23T01:24:06Z
dmvt marked issue #552 as primary and marked this issue as a duplicate of 552
#4 - c4-judge
2023-03-03T20:24:49Z
dmvt marked the issue as satisfactory
152.2651 USDC - $152.27
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L447-L466
The high water mark is used by Vault.sol to determine whether or not to take a performance fee. If the value of convertToAssets(1e18) > HWM and performanceFee > 0 then a fee is taken. However, highWaterMark is default set to 1e18 in the code. As a result a token that has less than 18 decimals of precision will almost always result in a convertToAssets()
value <1e18, in which case no fee will be collected even as the vault/adapter accumulates value. This occurs despite the use of syncFeeCheckpoint()
. This
https://gist.github.com/jhweintraub/f1c420711ff429bacd3e52fd1afa241d
The value of highWaterMark should be set in the initialize()
function as based on the underlying decimals of the want
token.
highWaterMark = IERC20(asset).decimals();
#0 - c4-judge
2023-02-16T05:54:14Z
dmvt marked the issue as primary issue
#1 - c4-sponsor
2023-02-17T13:26:04Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T21:27:27Z
dmvt marked issue #306 as primary and marked this issue as a duplicate of 306
#3 - c4-judge
2023-02-23T22:53:48Z
dmvt marked the issue as partial-50
#4 - c4-judge
2023-02-28T22:57:21Z
dmvt marked the issue as duplicate of #365
#5 - c4-judge
2023-02-28T23:01:09Z
dmvt marked the issue as not a duplicate
#6 - c4-judge
2023-02-28T23:01:18Z
dmvt marked the issue as duplicate of #306
#7 - c4-judge
2023-02-28T23:58:51Z
dmvt marked the issue as satisfactory
🌟 Selected for report: IllIllI
Also found by: 0x3b, 0xAgro, 0xBeirao, 0xMirce, 0xNineDec, 0xRobocop, 0xSmartContract, 0xTraub, 0xWeiss, 2997ms, 41i3xn, Awesome, Aymen0909, Bauer, Bnke0x0, Breeje, Cryptor, DadeKuma, Deathstore, Deekshith99, DevABDee, DevTimSch, Dewaxindo, Diana, Ermaniwe, Guild_3, H0, IceBear, Inspectah, JDeryl, Kaiziron, Kaysoft, Kenshin, Mukund, Praise, RaymondFam, Rickard, Rolezn, Ruhum, Sathish9098, SkyWalkerMan, SleepingBugs, UdarTeam, Udsen, Walter, aashar, adeolu, apvlki, arialblack14, ast3ros, btk, chaduke, chandkommanaboyina, chrisdior4, climber2002, codetilda, cryptonue, cryptostellar5, csanuragjain, ddimitrov22, descharre, dharma09, doublesharp, eccentricexit, ethernomad, fs0c, georgits, halden, hansfriese, hashminer0725, immeas, lukris02, luxartvinsec, matrix_0wl, merlin, mookimgo, mrpathfindr, nadin, olegthegoat, pavankv, rbserver, rebase, savi0ur, sayan, scokaf, seeu, shark, simon135, tnevler, tsvetanovv, ulqiorra, ustas, waldenyan20, y1cunhui, yongskiws, yosuke
35.4779 USDC - $35.48
Pool2SingleAssetCompounder.sol -> Adapter may be ruled incompatible for an adapter in which a single-hop trade router does not exist even when a 2-step route does. This limits the applicability of adapters that would fail to initialize because no such hop exists. Consider using an external contract for trade routers or allowing a user to submit their own
tradePath[0] = rewardTokens[i];
uint256[] memory amountsOut = IUniswapRouterV2(router).getAmountsOut(ERC20(asset).decimals() ** 10, tradePath); if (amountsOut[amountsOut.length] == 0) revert NoValidTradePath();
By default all tokens will be allowed until otherwise denied. This is bad opsec and should be limited at first and then more tokens allowed later.
function _verifyToken(address token) internal view { if ( ( permissionRegistry.endorsed(address(0)) ? !permissionRegistry.endorsed(token) : permissionRegistry.rejected(token)
MultiRewardEscrow.sol -> if fee amount is zero due to rounding the 1e18 in the calculation then an error NoFee should be thrown if (feePerc > 0) { uint256 fee = Math.mulDiv(amount, feePerc, 1e18);
amount -= fee; token.safeTransfer(feeRecipient, fee); }
Similarly, Error NoFee(IERC20) is never used. If accidental then function setFees() should check that each fee is nonzero when executing a new fee, otherwise remove dead-code.
MultiRewardEscrow.sol -> block.timestamp can be manipulated by validators. Consider using block.number instead, with duration equaling number_of_slots * seconds_per_slot (12)
AdapterBase.sol -> withdraw()
and redeem()
not tagged nonReentrant. While vault does, the adapter makes calls to external contracts
not owned by the protocol/user and so extra care should be applied
#0 - c4-judge
2023-02-27T14:43:51Z
dmvt marked the issue as grade-c
#1 - c4-judge
2023-03-01T00:15:19Z
dmvt marked the issue as grade-b