GoGoPool contest - Breeje's results

Liquid staking for Avalanche.

General Information

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

GoGoPool

Findings Distribution

Researcher Performance

Rank: 62/111

Findings: 3

Award: $92.55

๐ŸŒŸ Selected for report: 0

๐Ÿš€ Solo Findings: 0

Awards

14.9051 USDC - $14.91

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-209

External Links

Lines of code

https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/tokens/upgradeable/ERC4626Upgradeable.sol#L120-L124

Vulnerability details

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.

Proof of Concept

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.

Impact

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

Findings Information

Awards

20.4404 USDC - $20.44

Labels

bug
2 (Med Risk)
partial-50
sponsor disputed
duplicate-478

External Links

Lines of code

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

Vulnerability details

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.

Proof of Concept

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:

  • 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.
  • Alice will also deposit a 10,000 to take as much shares as possible.
  • After 1 rewards cycle of 1 day, redeem() to take the yields of 70.

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.

  • For the 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

Findings Information

๐ŸŒŸ Selected for report: 0xbepresent

Also found by: 0xdeadbeef0x, Breeje, Franfran, IllIllI, Matin, Rolezn, SmartSek, btk, ck, datapunk, fs0c, nadin, rvierdiiev, unforgiven

Awards

57.1995 USDC - $57.20

Labels

bug
2 (Med Risk)
satisfactory
duplicate-317

External Links

Lines of code

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

Vulnerability details

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.

Proof of Concept

Given:

  • rewardsCycleLength = 14 days
  • Alice deposit() 100 AVAX tokens
  • 5 AVAX tokens are transferred as rewards and syncRewards() was called
  • 1 day later, Alice redeem() with all shares, the transaction will revert at beforeWithdraw().

Aliceโ€™s shares worth 105 AVAX at this moment, but totalReleasedAssets = 100, making totalReleasedAssets -= amount reverts due to underflow.

Bob deposit() 5 AVAX tokens;

  • Alice withdraw() 105 AVAX tokens, totalReleasedAssets becomes 0;
  • Bob canโ€™t even withdraw 1 wei of AVAX token, as totalReleasedAssets is now 0.
  • If there are no new deposits, both Alice and Bob wonโ€™t be able to withdraw any of their funds until 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;
    }
  }

Link to code

#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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax ยฉ 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter