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: 76/169
Findings: 3
Award: $94.72
π Selected for report: 0
π Solo Findings: 0
π 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/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L134-L162 https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L294-L301
Vault share price can be maliciously inflated on the initial deposit, leading to the next depositor losing assets due to precision issues.
147: shares = convertToShares(assets) - feeShares;
If feeShares = 0 the first depositor of Vault can maliciously manipulate the share price by depositing the lowest possible amount (1 wei) of liquidity and then artificially inflating Vault.totalAssets.
This can inflate the base share price as high as 1:1e18 early on, which force all subsequence deposit to use this share price as a base and worst case, due to rounding down, if this malicious initial deposit front-run someone else depositing, this depositor will receive 0 shares and lost his deposited assets.
Manual Review
This is a well-known issue, Uniswap and other protocols had similar issues when supply == 0. For the first deposit, mint a fixed amount of shares.
#0 - c4-judge
2023-02-16T03:31:19Z
dmvt marked the issue as duplicate of #15
#1 - c4-sponsor
2023-02-18T11:54:55Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-judge
2023-02-23T00:37:24Z
dmvt marked the issue as partial-50
#3 - c4-judge
2023-02-23T01:05:07Z
dmvt changed the severity to 3 (High Risk)
#4 - c4-judge
2023-03-01T00:41:15Z
dmvt marked the issue as satisfactory
44.9555 USDC - $44.96
https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L294-L301 https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L211-L240
Malicious users can withdraw the assets from the vault for free, effectively allowing them to drain the assets of the vault.
[H-01] Malicious Users Can Drain The Assets Of Vault The Vault.convertToShares function relies on the mulDiv Rounding Down function in Line 300 when calculating the number of shares needed in exchange for a certain number of assets. Note that the computation is rounded down, therefore, if the result is less than 1 (e.g. 0.9), Solidity will round them down to zero. Thus, it is possible that this function will return zero. File: Vault.sol https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L294-L301
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); }
The Vault.withdraw function relies on the Vault.convertToShares function. In certain conditions, the Vault.convertToShares function in Line 300 will return zero if the withdrawal amount causes the division within the Vault.convertToShares function to round down to zero (usually due to a small amount of withdrawal amount).
If the Vault.convertToShares function in Line 300 returns zero, no shares will be burned at Line 233. Subsequently, in Line 233 , the contract will transfer the assets to the users. As a result, the users receive the assets without burning any of their shares, effectively allowing them to receive assets for free. https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L233
233: _burn(owner, shares);
Assume that the vault with the following state:
Total Asset = 1000 WETH Total Supply = 10 shares Assume that Alice wants to withdraw 99 WETH from the vault. Thus, she calls the Vault.withdraw(99 WETH) function. The Vault.convertToShares function will compute the number of shares that Alice needs to burn in exchange for 99 WETH. However, since Solidity rounds 0.99 down to 0, Alice does not need to burn a single share. She will receive 99 WETH for free.
assets.mulDiv(supply, totalAssets(), Math.Rounding.Down) 99 WETH.mulDiv(10 shares, 1000 WETH, Math.Rounding.Down) (99 * 10) / 1000 990 / 1000 = 0.99 = 0
Manual Review
Ensure that at least 1 share is burned when the users withdraw their assets. This can be mitigated by use the adapter.previewWithdraw function instead convertToShares function to round up instead of round down by when computing the number of shares to be burned. https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L329-L337 https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L343-L350
function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { return _previewWithdraw(assets); }
#0 - c4-judge
2023-02-16T07:57:35Z
dmvt marked the issue as duplicate of #71
#1 - c4-sponsor
2023-02-18T12:13:51Z
RedVeil marked the issue as sponsor confirmed
#2 - c4-sponsor
2023-02-18T12:14:01Z
RedVeil marked the issue as disagree with severity
#3 - c4-judge
2023-02-23T00:23:53Z
dmvt marked the issue as satisfactory
#4 - c4-judge
2023-02-23T01:14:41Z
dmvt changed the severity to 2 (Med Risk)
π 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
Rather than using abi.encodePacked for appending bytes, since version 0.8.4, bytes.concat() is enabled Since version 0.8.4 for appending bytes, bytes.concat() can be used instead of abi.encodePacked(,). Context: 06 File: src/utils/MultiRewardEscrow.sol
104: bytes32 id = keccak256(abi.encodePacked(token, account, amount, nonce));
File: src/utils/MultiRewardStaking.sol
49: _name = string(abi.encodePacked("Staked ", IERC20Metadata(address(_stakingToken)).name())); 50: _symbol = string(abi.encodePacked("pst-", IERC20Metadata(address(_stakingToken)).symbol())); 461: abi.encodePacked(
https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L49-L50 https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L461
File: src/vault/Vault.sol
94: abi.encodePacked("Popcorn", name(), block.timestamp, "Vault") 680: abi.encodePacked(
https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L94 https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L680
File: src/vault/adapter/abstracts/AdapterBase.sol
648: abi.encodePacked(
Context: 03 Using ecrecover is against best practice. Preferably use ECDSA.recover instead. EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature unique. However it should be impossible to be a threat by now. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol
File: src/utils/MultiRewardStaking.sol
459: address recoveredAddress = ecrecover(
File: src/vault/Vault.sol
678: address recoveredAddress = ecrecover(
File: src/vault/adapter/abstracts/AdapterBase.sol
646: address recoveredAddress = ecrecover(
All contract
Not all IERC20 implementations revert() when there's a failure in approve(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything File: src/utils/MultiRewardStaking.sol , src/vault/Vault.sol , src/vault/VaultController.sol https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardStaking.sol https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/VaultController.sol
Context: All Contracts
Description: It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability. https://docs.soliditylang.org/en/v0.8.15/natspec-format.html
Recommendation: NatSpec comments should be increased in contracts
#0 - c4-judge
2023-02-28T17:36:33Z
dmvt marked the issue as grade-b