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: 6/92
Findings: 9
Award: $3,540.15
🌟 Selected for report: 1
🚀 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
All Eth can be drained by fake vault addresses. https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantSavETHVaultPool.sol#L29
In batchDepositETHForStaking
, _savETHVault
is checked for its validity through
SavETHVault savETHPool = SavETHVault(_savETHVaults[i]); require( liquidStakingDerivativeFactory.isLiquidStakingManager(address(savETHPool.liquidStakingManager())), "Invalid liquid staking manager" );
However, an attacker can create a fake contract that retuns a correct liquidStakingNetworkManager
, thus passing the check easily.
After the check, any ETH in the pool will be sent to an address this fake contract provide:
savETHPool.batchDepositETHForStaking{ value: transactionAmount }( _blsPublicKeys[i], _stakeAmounts[i] );
manual
Always passing liquid staking manager address, checking its real and then requesting either the savETH vault or staking funds vault is a good idea rather than other way around from a giant pool perspective.
#0 - c4-judge
2022-11-21T22:20:40Z
dmvt marked the issue as duplicate of #36
#1 - c4-judge
2022-12-02T21:54:05Z
dmvt marked the issue as satisfactory
#2 - C4-Staff
2022-12-21T05:40:16Z
JeeberC4 marked the issue as duplicate of #36
#3 - C4-Staff
2022-12-22T08:15:15Z
liveactionllama marked the issue as not a duplicate
#4 - C4-Staff
2022-12-22T08:15:33Z
liveactionllama marked the issue as duplicate of #251
🌟 Selected for report: rotcivegaf
Also found by: 0x4non, clems4ever, datapunk
102.5291 USDC - $102.53
potential reentrancy risk in _distributeETHRewardsToUserForToken
Reentrancy risk can potentially be demaging
_distributeETHRewardsToUserForToken
, which in turn calls (bool success, ) = _recipient.call{value: due}("");
. As recipient of ETH, the msg.sender can potentially reenter.
_distributeETHRewardsToUserForToken
is being used in multiple places, such as
batchDepositETHForStaking
in SavETHVault
beforeTokenTransfer
in SavETHVault
batchDepositETHForStaking
in StakingFundsVault
beforeTokenTransfer
in StakingFundsVault
manual
Careful examination and consider adding reentrancyGuard
to above functions.
#0 - c4-judge
2022-11-21T23:20:40Z
dmvt marked the issue as duplicate of #35
#1 - c4-judge
2022-11-29T15:23:07Z
dmvt marked the issue as satisfactory
#2 - c4-judge
2022-11-29T15:53:48Z
dmvt changed the severity to 3 (High Risk)
#3 - c4-judge
2022-11-29T15:53:54Z
dmvt marked the issue as partial-25
#4 - C4-Staff
2022-12-21T00:17:14Z
JeeberC4 marked the issue as duplicate of #35
#5 - liveactionllama
2022-12-22T09:24:46Z
Duplicate of #328
🌟 Selected for report: datapunk
2925.3837 USDC - $2,925.38
real LPTokens
can be transferred out of GiantMevAndFeesPool
through fake _stakingFundsVaults
provided by an attacker.
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L126
bringUnusedETHBackIntoGiantPool
takes in _stakingFundsVaults
, _oldLPTokens
, _newLPTokens
and rotate _amounts
from old to new tokens. The tokens are thoroughly verified by burnLPForETH
in ETHPoolLPFactory
.
However, theres is no checking for the validity of _stakingFundsVaults
, nor the relationship between LPTokens
and _stakingFundsVaults
. Therefore, an attacker can create fake contracts for _stakingFundsVaults
, with burnLPTokensForETH
, that takes LPTokens
as parameters. The msg.sender
in burnLPTokensForETH
is GiantMevAndFeesPool
, thus the attacker can transfer LPTokens
that belongs to GiantMevAndFeesPool
to any addresses it controls.
manual
Always passing liquid staking manager address, checking its real and then requesting either the savETH vault or staking funds vault is a good idea to prove the validity of vaults.
#0 - dmvt
2022-11-21T22:39:12Z
This one also seems wrong. The only real effect I can see is the attacker's savETH vault getting attacker supplied parameters passed back to it. Leaving open for sponsor review.
#1 - c4-judge
2022-11-21T22:39:17Z
dmvt marked the issue as primary issue
#2 - c4-sponsor
2022-11-28T16:40:15Z
vince0656 marked the issue as sponsor confirmed
#3 - vince0656
2022-11-28T16:40:46Z
Duplicate of #365, #364, #363
#4 - c4-sponsor
2022-11-28T16:41:07Z
vince0656 requested judge review
#5 - vince0656
2022-11-28T16:41:20Z
please see notes about duplicates above
#6 - dmvt
2022-12-02T21:58:30Z
I've nullified the other 3 since all four are from the same warden.
#7 - c4-judge
2022-12-02T22:29:10Z
dmvt marked the issue as satisfactory
#8 - c4-judge
2022-12-02T22:29:13Z
dmvt marked the issue as selected for report
🌟 Selected for report: 0xdeadbeef0x
22.1463 USDC - $22.15
GiantPoolBase
keeps track of idleETH
, which can be potentially wrong if Eth
are sent directly into the contract not through depositETH
. When that happens, idleETH
is incorrect.
function depositETH(uint256 _amount) public payable { idleETH += msg.value; }
manual
add a receive function:
receive() external payable { depositETH(msg.sender); }
#0 - c4-judge
2022-11-21T22:44:43Z
dmvt marked the issue as duplicate of #74
#1 - c4-judge
2022-11-21T22:44:50Z
dmvt marked the issue as partial-25
#2 - c4-judge
2022-11-30T11:50:44Z
dmvt marked the issue as satisfactory
33.2194 USDC - $33.22
User's lpTokenETH
and _lpTokens
will be out-of-sync
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantSavETHVaultPool.sol#66
In withdrawDETH
, vault
and token
are checked for their validity through
_assertUserHasEnoughGiantLPToClaimVaultLP(token, amount);
which, in turn, checks
require(_amount >= MIN_STAKING_AMOUNT, "Invalid amount"); require(_token.balanceOf(address(this)) >= _amount, "Pool does not own specified LP");
and
require(vault.isDETHReadyForWithdrawal(address(token)), "dETH is not ready for withdrawal");
However, if the website gets hacked and when users interact with withdrawDETH
, they might be supplying fake _savETHVaults
and _lpTokens
as specified by the attacker.
These fake contracts can return anything needed to pass the above checks easily.
After theses checks, users' legitimate lpTokenETH
will be burned:
lpTokenETH.burn(msg.sender, amount);
but only fake lpTokens
will be burned:
getDETH().transfer(msg.sender, dETHReceivedFromAllSavETHVaults);
manual
Always passing liquid staking manager address, checking its real and then requesting either the savETH vault or staking funds vault is a good idea to prove the validity of vaults.
#0 - c4-judge
2022-11-21T22:21:37Z
dmvt marked the issue as duplicate of #98
#1 - c4-judge
2022-11-30T12:31:04Z
dmvt marked the issue as satisfactory
#2 - c4-judge
2022-11-30T12:31:13Z
dmvt marked the issue as partial-50
#3 - c4-judge
2022-11-30T12:31:20Z
dmvt changed the severity to 2 (Med Risk)
44.2926 USDC - $44.29
Judge has assessed an item in Issue #373 as M risk. The relevant finding follows:
N2. ETH not accumulated in previewAccumulatedETH https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L91 supposed to have accumulated += ... Although it is an external view function, depending on its usages, it may present more issues to the callers.
#0 - c4-judge
2022-12-02T22:00:20Z
dmvt marked the issue as duplicate of #160
#1 - c4-judge
2022-12-02T22:00:34Z
dmvt marked the issue as partial-50
303.7898 USDC - $303.79
If trusted manager supplies a wrong address, the funds might be non-retrievable.
in withdrawETHForStaking
, the manager can supply any _smartWallet
to withdraw ETH, which the _smartWallet
can be very easily checked against OwnableSmartWalletFactory
, since it keeps track of them in mapping(address => bool) public walletExists;
Despite the fact this function is only callable by the trusted manager, it is quite be a scary mistake if wrong address is used.
manual
check supplied _smartWallet
against walletExists
from OwnableSmartWalletFactory
#0 - c4-judge
2022-11-21T22:42:24Z
dmvt marked the issue as duplicate of #317
#1 - c4-judge
2022-12-02T19:56:21Z
dmvt marked the issue as satisfactory
45.5685 USDC - $45.57
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/ETHPoolLPFactory.sol#L111 https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/ETHPoolLPFactory.sol#L80
Deposits tolpToken
can be made such as it will never reach 24 ether as required
Funds can be deposited into lpToken
through either _depositETHForStaking
or 'rotateLPTokens', both have:
require(_amount >= MIN_STAKING_AMOUNT, "Min amount not reached");
Both also requires that the total deposit per lpToken
does not exceed 24 ether:
_depositETHForStaking
:
require(lpToken.totalSupply() + _amount <= maxStakingAmountPerValidator
require(_amount <= maxStakingAmountPerValidator
and rotateLPTokens
:
require(_amount + _newLPToken.totalSupply() <= 24 ether, "Not enough mintable tokens");
.
An attacker can monitor deposits and front-running them to make the totalSupply in the range of (23.999, 24). In such cases, users would have to withdraw their ETH to correct.
manual
in deposit and rotate, adding require(amount % MIN_STAKING_AMOUNT == 0);
will prevent such attacks.
#0 - c4-judge
2022-11-21T22:40:29Z
dmvt marked the issue as duplicate of #140
#1 - c4-judge
2022-11-30T14:01:02Z
dmvt changed the severity to 2 (Med Risk)
#2 - c4-judge
2022-11-30T14:01:27Z
dmvt marked the issue as partial-25
#3 - c4-judge
2022-11-30T14:01:56Z
dmvt marked the issue as satisfactory
#4 - C4-Staff
2022-12-21T00:06:23Z
JeeberC4 marked the issue as duplicate of #422
🌟 Selected for report: 0xSmartContract
Also found by: 0x4non, 0xNazgul, 0xRoxas, 0xdeadbeef0x, 0xmuxyz, 9svR6w, Awesome, Aymen0909, B2, Bnke0x0, CloudX, Deivitto, Diana, Franfran, IllIllI, Josiah, RaymondFam, ReyAdmirado, Rolezn, Sathish9098, Secureverse, SmartSek, Trust, Udsen, a12jmx, aphak5010, brgltd, bulej93, c3phas, ch0bu, chaduke, chrisdior4, clems4ever, cryptostellar5, datapunk, delfin454000, fs0c, gogo, gz627, hl_, immeas, joestakey, lukris02, martin, nogo, oyc_109, pashov, pavankv, peanuts, pedr02b2, rbserver, rotcivegaf, sahar, sakman, shark, tnevler, trustindistrust, zaskoh, zgo
52.0338 USDC - $52.03
The way claims are calculated, it is dividing by _numOfShares first, then multiply by _balanceOfSende:
uint256 newAccumulatedETH = accumulatedETHPerLPShare + ((unprocessed * PRECISION) / _numOfShares);
((newAccumulatedETH * _balanceOfSender) / PRECISION)
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/ETHPoolLPFactory.sol#43
However, since PRECISION is present, I am not sure how much the impact is.
Just fruit for thought to potentially refactor the formula
previewAccumulatedETH
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L91 supposed to have
accumulated += ...
Although it is an external view function, depending on its usages, it may present more issues to the callers.
_addPriorityStakers
, duplicated stakers are pass the dup checkhttps://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/syndicate/Syndicate.sol#L588 checks
if (i > 0 && staker < _priorityStakers[i-1]) revert DuplicateArrayElements();
while it should check
if (i > 0 && staker <= _priorityStakers[i-1]) revert DuplicateArrayElements();
require(stakingFundsLP.totalSupply() == 4 ether, "DAO staking funds vault balance must be at least 4 ether");
should be:
require(stakingFundsLP.totalSupply() == 4 ether, "DAO staking funds vault balance must have 4 ether");
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L940
updateAccumulatedETHPerLP
in StakingFundsVault
, the last thing _claimFundsFromSyndicateForDistribution
does is to call updateAccumulatedETHPerLP
, yet everywhere _claimFundsFromSyndicateForDistribution
is called, it is followed by updateAccumulatedETHPerLP
. The 2nd does nothing, so can be removed.
unstake
in Syndicate
does not check for 0 amount.https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/syndicate/Syndicate.sol#L259 There is no minimum value for _sETHAmount to be unstaked. When 0 amount is supplied, it should be checked and skipped early.
Add require(_pool != address(0))
to
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantLP.sol#L25
Add require(_liquidStakingManager != address(0))
to
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/OptionalGatekeeperFactory.sol#L12
Add require(_manager != address(0))
to
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/OptionalHouseGatekeeper.sol#L14
if (_recipient == address(this)) revert ZeroAddress();
if (getSlotRegistry().currentSlashedAmountOfSLOTForKnot(blsPubKey) != 0) revert InvalidNumberOfCollateralizedOwners();
contract GiantPoolBase
abstract#0 - c4-judge
2022-12-02T21:59:32Z
dmvt marked the issue as grade-b