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: 61/169
Findings: 3
Award: $161.60
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: Lirios
Also found by: KIntern_NA, csanuragjain, hansfriese, ladboy233, thecatking
114.1988 USDC - $114.20
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L594-L613
On changing adapter address, all funds are first withdrawn from old adapter and then reinvested in new adaptor. In case of some adaptor like BeefyAdapter
, an extra fees (strat.withdrawalFee()) is also deducted on withdrawal. It was observed that proposedAdapter
is never reset meaning user can call changeAdapter
multiple times causing protocol to loss funds as withdrawal fees
proposeAdapter
functionfunction proposeAdapter(IERC4626 newAdapter) external onlyOwner { if (newAdapter.asset() != address(asset)) revert VaultAssetMismatchNewAdapterAsset(); proposedAdapter = newAdapter; proposedAdapterTime = block.timestamp; emit NewAdapterProposed(newAdapter, block.timestamp); }
proposedAdapterTime
owner calls changeAdapter
which changes adapter to BeefyAdapterfunction changeAdapter() external takeFees { if (block.timestamp < proposedAdapterTime + quitPeriod) revert NotPassedQuitPeriod(quitPeriod); adapter.redeem( adapter.balanceOf(address(this)), address(this), address(this) ); asset.approve(address(adapter), 0); emit ChangedAdapter(adapter, proposedAdapter); adapter = proposedAdapter; asset.approve(address(adapter), type(uint256).max); adapter.deposit(asset.balanceOf(address(this)), address(this)); }
Attacker A calls changeAdapter
function again
Since proposedAdapter and proposedAdapterTime are not reset, this works
//Pass since adapterTime was not reset if (block.timestamp < proposedAdapterTime + quitPeriod) revert NotPassedQuitPeriod(quitPeriod);
function previewRedeem(uint256 shares) public view override returns (uint256) { uint256 assets = _convertToAssets(shares, Math.Rounding.Down); IBeefyStrat strat = IBeefyStrat(beefyVault.strategy()); uint256 beefyFee = strat.withdrawalFee(); if (beefyFee > 0) assets -= assets.mulDiv( beefyFee, BPS_DENOMINATOR, Math.Rounding.Up ); return assets; }
Due to withdrawal fees, not full amount is redeposited and the fees is then distributed to network users. Even though there was no change in adapter
Repeating these steps will cause growing losses
Revise the changeAdapter
function to change adaptor only if provided is different from existing adapter
function changeAdapter() external takeFees { ... require(proposedAdapter!=adapter, "Same adapter"); ... }
#0 - c4-judge
2023-02-16T07:17:32Z
dmvt marked the issue as primary issue
#1 - c4-sponsor
2023-02-17T10:11:40Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-25T21:03:23Z
dmvt changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-02-25T21:08:28Z
dmvt marked issue #515 as primary and marked this issue as a duplicate of 515
#4 - c4-judge
2023-02-28T17:31:48Z
dmvt marked the issue as satisfactory
🌟 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
11.9167 USDC - $11.92
https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardEscrow.sol#L100
If you are making a Lock fund for escrow using a fee on transfer token then contract will receive less amount (X-fees) but will record full amount (X). This becomes a problem as when claim is made then call will fail due to lack of funds. Worse, one user will unknowingly take the missing fees part from another user deposited escrow fund
lock
function which transfer funds from user to contractfunction lock( IERC20 token, address account, uint256 amount, uint32 duration, uint32 offset ) external { ... token.safeTransferFrom(msg.sender, address(this), amount); ... escrows[id] = Escrow({ token: token, start: start, end: start + duration, lastUpdateTime: start, initialBalance: amount, balance: amount, account: account }); ... }
Since token has fee on transfer so contract receives only amount-fees
but the escrow object is created for full amount
Lets say escrow duration is over and claim is made using claimRewards
function
function claimRewards(bytes32[] memory escrowIds) external { ... uint256 claimable = _getClaimableAmount(escrow); ... escrow.token.safeTransfer(escrow.account, claimable); ... }
amount
. But this fails on transfer to account since contract has only amount-fees
Compute the balance before and after transfer and subtract them to get the real amount. Also use nonReentrant while using this to prevent from reentrancy in ERC777 tokens
#0 - c4-judge
2023-02-16T07:07:17Z
dmvt marked the issue as duplicate of #44
#1 - c4-sponsor
2023-02-18T11:48:12Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T01:21:07Z
dmvt marked the issue as selected for report
🌟 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
Contract: https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/CloneRegistry.sol#L41
Issue:
On adding a duplicate entry via addClone
function, multiple clone will be written on clones
and allClones
object.
Impact: If any future function relies directly on number of clones, or iterating allClones then it will encounter duplicate entry. if its not operated correctly then could result in incorrect outcome
Recommendation: Add below condition:
require(!cloneExists[clone], "Already exists");
#0 - c4-judge
2023-02-28T15:03:06Z
dmvt marked the issue as grade-b