Platform: Code4rena
Start Date: 11/11/2022
Pot Size: $90,500 USDC
Total HM: 52
Participants: 92
Period: 7 days
Judge: LSDan
Total Solo HM: 20
Id: 182
League: ETH
Rank: 86/92
Findings: 3
Award: $44.41
🌟 Selected for report: 0
🚀 Solo Findings: 1
🌟 Selected for report: Jeiwan
Also found by: 0xdeadbeef0x, 9svR6w, JTJabba, Lambda, Trust, arcoun, banky, bin2chen, bitbopper, c7e7eff, clems4ever, datapunk, fs0c, hihen, imare, immeas, perseverancesuccess, ronnyx2017, satoshipotato, unforgiven, wait
11.192 USDC - $11.19
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantSavETHVaultPool.sol#L29-L60 https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L28-L53
Attacker can take away all eth in the pools.
Both GiantSavETHVaultPool and GiantMevAndFeesPool provide a function - batchDepositETHForStaking to deposit ETH from pool to vault for staking.
The main logic is:
for (uint256 i; i < numOfSavETHVaults; ++i) { uint256 transactionAmount = _ETHTransactionAmounts[i]; // As ETH is being deployed to a savETH pool vault, it is no longer idle idleETH -= transactionAmount; SavETHVault savETHPool = SavETHVault(_savETHVaults[i]); require( liquidStakingDerivativeFactory.isLiquidStakingManager(address(savETHPool.liquidStakingManager())), "Invalid liquid staking manager" ); // Deposit ETH for staking of BLS key savETHPool.batchDepositETHForStaking{ value: transactionAmount }( _blsPublicKeys[i], _stakeAmounts[i] ); }
It will check the vault by the liquidStakingManager address, and then send ETH to the vault by calling batchDepositETHForStaking
.
Attacker can deploy a mock vault contract (M) which implements following two functions:
function isLiquidStakingManager()
returns the correct liquidStakingManager address.functoin batchDepositETHForStaking(bytes[], uint256[]) payable
to receive ETH.Then the attacker can call batchDepositETHForStaking of the pools with the mock vault contract M, and ETH in pools will be sent to M directly.
n/a
We need check the vault addresses passed to batchDepositETHForStaking, not to check the liquidStakingManager returned by the vault contract.
An easy way to do this is to register all newly created valid vault contracts in a trusted contract like the liquidStakingDerivativeFactory(LSDNFactory), and then check within the batchDepositETHForStaking to see if vault is in the registry.
#0 - c4-judge
2022-11-21T16:27:19Z
dmvt marked the issue as duplicate of #36
#1 - c4-judge
2022-11-29T15:42:46Z
dmvt marked the issue as satisfactory
#2 - C4-Staff
2022-12-21T05:40:16Z
JeeberC4 marked the issue as duplicate of #36
#3 - liveactionllama
2022-12-22T08:44:08Z
Duplicate of #251
33.2194 USDC - $33.22
Anyone can exchange ETH for any token in the pool at a ratio of 1:1
The main logic of withdrawLPTokens function in GiantPoolBase is:
// Burn giant LP from user before sending them an LP token from this pool lpTokenETH.burn(msg.sender, amount); // Giant LP tokens in this pool are 1:1 exchangeable with external savETH vault LP token.transfer(msg.sender, amount);
The function doesn't check what the token passed in is, and calls token.transfer(msg.sender, amount);
directly after eth lp is burnt.
If there is a token T with higher price than eth in the pool, then an attacker can pass in the address of the token T as a lpToken to swap ETH for T.
n/a
Register all LPTokens in a global trusted contract (e.g. LSDNFactory - liquidStakingDerivativeFactory), and check the passed in lpToken addresses in the registry.
#0 - c4-judge
2022-11-21T17:32:00Z
dmvt marked the issue as duplicate of #98
#1 - c4-judge
2022-11-30T12:32:20Z
dmvt marked the issue as satisfactory
#2 - c4-judge
2022-11-30T12:32:25Z
dmvt marked the issue as partial-50