Platform: Code4rena
Start Date: 05/01/2023
Pot Size: $90,500 USDC
Total HM: 55
Participants: 103
Period: 14 days
Judge: Picodes
Total Solo HM: 18
Id: 202
League: ETH
Rank: 65/103
Findings: 1
Award: $69.09
🌟 Selected for report: 0
🚀 Solo Findings: 0
69.0905 USDC - $69.09
The first transfer in a vault that is based on ERC4626Cloned produces different results depending on whether it is done via deposit or via mint. While first deposit produces a number of shares that is 1:1 with the asset, in the case of mint, the results may vary:
This means that if the first mint is for a small number of shares (say N
), the minimum deposit from there on will be given by 10 ethers / N
or in other words the minimum transfer to produce at least 1 share will be very large.
function testLargeTransferZeroShares() public { TestNFT nft = new TestNFT(1); address tokenContract = address(nft); uint256 tokenId = uint256(0); uint256 initialBalance = WETH9.balanceOf(address(this)); // create a PublicVault with a 14-day epoch address publicVault = _createPublicVault({strategist: strategistOne, delegate: strategistTwo, epochLength: 14 days}); PublicVault pv = PublicVault(publicVault); address joe = address(0x70a57ed); vm.deal(joe, 20 ether); vm.startPrank(joe); WETH9.deposit{value: 20 ether}(); WETH9.approve(publicVault, 20 ether); uint256 assets = pv.mint(1, joe); vm.expectRevert(bytes("ZERO_SHARES")); pv.deposit(9.99 ether, joe); vm.stopPrank(); }
Test results:
Running 1 test for src/test/AstariaTest.t.sol:AstariaTest [PASS] testLargeTransferZeroShares() (gas: 1351952) Test result: ok. 1 passed; 0 failed; finished in 13.69ms
n/a
Mind and deposit previews are calculated using the following formulas.
previewMint(uint256 shares) => totalSupply() == 0 ? 10e18 : shares * totalAssets() / totalSupply(); previewDeposit(uint256 assets) => totalSupply() == 0 ? assets : assets * totalSupply() / totalAssets(); Either substitute `10e18` with the original `shares` in previewMint or ensure that if totalSupply is zero, then the number of shares requested (or issued) is also a large, matching value in order to produce a 1:1 initial ratio.
#0 - c4-judge
2023-01-23T16:27:03Z
Picodes marked the issue as duplicate of #588
#1 - Picodes
2023-01-23T16:27:25Z
The finding could be clearer as to how this could lead to a loss of fund
#2 - c4-judge
2023-01-25T15:27:31Z
Picodes marked the issue as not a duplicate
#3 - c4-judge
2023-01-25T15:27:43Z
Picodes marked the issue as duplicate of #588
#4 - c4-judge
2023-02-19T16:43:11Z
Picodes marked the issue as partial-50
#5 - Picodes
2023-02-19T16:47:55Z
No explanations of how this could lead to a loss of funds or a break in functionalities.
#6 - c4-judge
2023-02-19T16:48:03Z
Picodes marked the issue as not a duplicate
#7 - c4-judge
2023-02-19T16:48:11Z
Picodes marked the issue as duplicate of #588
#8 - c4-judge
2023-02-19T16:48:16Z
Picodes marked the issue as full credit
#9 - c4-judge
2023-02-19T16:58:07Z
Picodes changed the severity to 3 (High Risk)
#10 - c4-judge
2023-02-24T10:11:11Z
Picodes marked the issue as satisfactory
#11 - c4-judge
2023-02-24T10:11:17Z
Picodes marked the issue as partial-50
#12 - c4-judge
2023-02-24T10:22:42Z
Picodes marked the issue as satisfactory
69.0905 USDC - $69.09
If a user or a contract that has a large allowance (10 ethers or max) on an ERC4626Cloned based Vault that has not yet received any deposits, calls mint with 0 share argument, will have a 10 ethers of the asset transferred to the Vault with no way to reclaim it because he/it will receive no shares.
NOTE: Raising this issue as high risk conforming to judging criteria '3 — High: Assets can be stolen/*lost*/compromised directly'. No complains if judge feels that the necessary pre-conditions makes it a '2 — Med'.
function testAssetsLostToVault() public { TestNFT nft = new TestNFT(1); address tokenContract = address(nft); uint256 tokenId = uint256(0); uint256 initialBalance = WETH9.balanceOf(address(this)); // create a PublicVault with a 14-day epoch address publicVault = _createPublicVault({strategist: strategistOne, delegate: strategistTwo, epochLength: 14 days}); PublicVault pv = PublicVault(publicVault); address joe = address(0x70a57ed); vm.deal(joe, 10 ether); vm.startPrank(joe); WETH9.deposit{value: 10 ether}(); WETH9.approve(publicVault, 10 ether); uint256 assets = pv.mint(0, joe); emit log_string(string.concat("Joe's balance : ", Strings.toString(WETH9.balanceOf(address(joe))))); emit log_string(string.concat("Joe's shares : ", Strings.toString(pv.balanceOf(address(joe))))); emit log_string(string.concat("pv.totalAssets() : ", Strings.toString(pv.totalAssets()))); emit log_string(string.concat("pv.totalSupply() : ", Strings.toString(pv.totalSupply()))); emit log_string(string.concat("pv.previewRedeem(): ", Strings.toString(pv.previewRedeem(pv.balanceOf(address(joe)))))); vm.stopPrank(); }
Test results:
Running 1 test for src/test/AstariaTest.t.sol:AstariaTest [PASS] testAssetsLostToVault() (gas: 1307633) Logs: Joe's balance : 0 Joe's shares : 0 pv.totalAssets() : 10000000000000000000 pv.totalSupply() : 0 pv.previewRedeem(): 0 Test result: ok. 1 passed; 0 failed; finished in 29.48ms
n/a
Since the previewMint does not use the original supply == 0 ? shares : ..., at least add a require precondition to the mint function to prevent minting of 0 shares.
#0 - c4-judge
2023-01-24T08:58:38Z
Picodes marked the issue as duplicate of #588
#1 - Picodes
2023-01-24T08:59:09Z
Giving duplicate with partial credit as this report misses the main impact of the finding which is the possibility for the first user to trick the next ones
#2 - c4-judge
2023-01-24T08:59:14Z
Picodes marked the issue as partial-50
#3 - eierina
2023-01-24T12:53:01Z
Hi @Picodes
I trust this to be an oversight, please note that this is not the same issue you are mentioning (and indeed I have another issue open for the same you're mentioning) and it would still be present after the fix to https://github.com/code-423n4/2023-01-astaria-findings/issues/588.
This one is about the risk of calling mint passing 0 for shares argument. In this case, the message sender will lose 10 ethers and receive nothing in exchange therefore having no way to redeem or withdraw.
The original ERC4626 code emits "x" shares if supply is 0, where "x" is the same argument passed to the mint function, so in the original code there is no risk of passing 0 because it has no effects, but in this case it does have an effect and therefore the mint should be protected, which is not. Please check carefully the included test and the test outputs.
#4 - Picodes
2023-02-19T16:48:57Z
Hi @eierina, indeed I missed that you had another issue opened. In this case, I'll treat both reports separately.
This report successfully notices that there is an issue with previewMint
but the scenario described here is strictly speaking of Low severity at best. Indeed, calling the function with 0
as an argument would be an error on the user side as it doesn't make any sense. Therefore, protecting users from this falls within the Safety checks
category, which is QA.
#5 - Picodes
2023-02-19T16:51:21Z
Also, please note that you shouldn't comment on your own findings until post-judging QAs
and check the rules here: https://code4rena.notion.site/Backstage-Warden-Guidelines-06d1073540994cc08937f721c2951b0f
#6 - c4-judge
2023-02-24T10:22:37Z
Picodes marked the issue as satisfactory