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: 62/111
Findings: 3
Award: $92.55
๐ 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
convertToShares
function follow the formula:
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
The share price always return 1:1 with asset token. If everything work normally, share price will slowly increase with time to 1:2 or 1:10 as more rewards coming in.
Right after TokenggAVAX contract creation, during first cycle, any user can deposit 1 share set totalSupply = 1. And transfer token to vault to inflate totalAssets() before rewards kick in. (Basically, pretend rewards themselves before anyone can deposit in to get much better share price.)
This can inflate base share price as high as 1:1e18 early on, which force all subsequence deposit to use this share price as base.
New TokenggAVAX vault share price can be manipulated right after creation. Which give early depositor greater share portion of the vault during the first cycle.
This exploit is unique to contract similar to ERC4626. It only works if starting supply equals very small number and rewards cycle is very short. Or everyone withdraws, total share supply become close to 0.
This can be easily fix by making sure someone always deposited first so totalSupply become high enough that this exploit become irrelevant. Unless in unlikely case someone made arbitrage bot watching vault factory contract. Just force deposit early token during vault construction as last resort.
#0 - 0xminty
2023-01-04T02:16:13Z
dupe of #815
#1 - c4-judge
2023-01-08T13:12:02Z
GalloDaSballo marked the issue as duplicate of #209
#2 - c4-judge
2023-01-29T18:38:48Z
GalloDaSballo changed the severity to 3 (High Risk)
#3 - c4-judge
2023-02-08T09:41:11Z
GalloDaSballo marked the issue as satisfactory
๐ Selected for report: unforgiven
Also found by: 0x73696d616f, 0xNazgul, Bnke0x0, Breeje, HollaDieWaldfee, IllIllI, Rolezn, __141345__, btk, ck, csanuragjain, fs0c, joestakey, kiki_dev, koxuan, rvierdiiev
20.4404 USDC - $20.44
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/tokens/upgradeable/ERC4626Upgradeable.sol#L42-L54 https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/tokens/upgradeable/ERC4626Upgradeable.sol#L91-L112 https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/tokens/TokenggAVAX.sol#L88-L130
In the current rewards accounting, vault shares in deposit()
and redeem()
can not correctly record the spot yields generated by the staked asset. Yields are released over the next rewards cycle. As a result, malicious users can steal yields from innocent users by picking special timing to deposit()
and redeem()
.
If withdraw just after the rewardsCycleEnd
timestamp, a user can not get the yields from last rewards cycle. Since the totalAssets() only contain totalReleasedAssets
but not the yields part. It takes 1 rewards cycle to linearly add to the totalReleasedAssets
.
Assume per 10,000 asset staking generate yields of 70 for 7 days, and the reward cycle is 1 day. A malicious user Alice can do the following:
Effectively steal the yields from Bob. The profit for Alice is not 70, because after 1 day, her own deposit also generates some yield, in this example this portion is 1. At the end, Alice steal yield of amount 60.
lastRewardsAmt
not released, allow the users to redeem as it is linearly released later.#0 - 0xminty
2023-01-04T01:27:52Z
dupe of #863
#1 - GalloDaSballo
2023-01-10T10:02:16Z
Making primary for better example
#2 - c4-judge
2023-01-10T10:02:21Z
GalloDaSballo marked the issue as primary issue
#3 - emersoncloud
2023-01-16T19:38:11Z
similar to https://github.com/code-423n4/2022-12-gogopool-findings/issues/208, where we want to ensure that rewards cycles start as soon as possible
#4 - emersoncloud
2023-01-20T18:55:38Z
I think there is something to be said about incentivizing syncRewards
calls so they happen as soon as possible and the likely hood that Alice can frontrun a large deposit and sync rewards favorably for her decreases.
#5 - emersoncloud
2023-01-20T19:20:36Z
I'm having trouble understanding this line
Watch the mempool for withdraw(10,000) from account Bob, front run it with syncRewards(), so that the most recent yields of amount 70 from Bob will stay in the vault.
If Bob withdraws one second before or after the syncRewards
call his exchange ratio of ggAVAX <-> AVAX will not change much at all.
#6 - emersoncloud
2023-01-20T19:21:58Z
And For the lastRewardsAmt not released, allow the users to redeem as it is linearly released later
, is how the contract works now. the rewards amount are released linearly over the rewards period. I feel like I'm not understanding this report fully.
#7 - c4-judge
2023-02-03T10:28:09Z
GalloDaSballo marked the issue as duplicate of #478
#8 - c4-judge
2023-02-03T10:28:24Z
GalloDaSballo marked the issue as partial-50
#9 - GalloDaSballo
2023-02-03T10:28:34Z
In contrast to the original dups of 478 these dups are not as accurate, awarding half
๐ Selected for report: 0xbepresent
Also found by: 0xdeadbeef0x, Breeje, Franfran, IllIllI, Matin, Rolezn, SmartSek, btk, ck, datapunk, fs0c, nadin, rvierdiiev, unforgiven
57.1995 USDC - $57.20
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/tokens/TokenggAVAX.sol#L88-L109 https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/tokens/TokenggAVAX.sol#L241-L246
totalReleasedAssets
is a cached value of total assets which will only include the unlockedRewards when the whole cycle ends.
This makes it possible for totalReleasedAssets -= amount
to revert when the withdrawal amount exceeds totalReleasedAssets
, as the withdrawal amount may include part of the unlockedRewards in the current cycle.
Given:
Aliceโs shares worth 105 AVAX at this moment, but totalReleasedAssets = 100, making totalReleasedAssets -= amount reverts due to underflow.
Bob deposit() 5 AVAX tokens;
rewardsCycleEnd
Consider changing to:
File: contract/tokens/TokenggAVAX.sol function beforeWithdraw( uint256 amount, uint256 /* shares */ ) internal override { uint256 _totalReleasedAssets = totalReleasedAssets; if (amount >= _totalReleasedAssets) { uint256 _totalAssets = totalAssets(); lastRewardAmount -= _totalAssets - _totalReleasedAssets; lastSync = block.timestamp; totalReleasedAssets = _totalAssets - amount; } else { totalReleasedAssets = _storedTotalAssets - amount; } }
#0 - c4-judge
2023-01-08T17:01:23Z
GalloDaSballo marked the issue as duplicate of #317
#1 - c4-judge
2023-02-08T10:06:25Z
GalloDaSballo marked the issue as satisfactory