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: 61/92
Findings: 2
Award: $58.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
6.2548 USDC - $6.25
The LiquidStakingManager
contract contains functionality via updateNodeRunnerWhitelistStatus()
to add or remove addresses from a whitelist. However, due to a flaw in a require
check, the function will automatically revert and essentially cannot be used. This error causes a cascading issue in _isNodeRunnerValid()
because if the whitelisting functionality is also enabled, no node runner could be set to true
, causing this function to bubble up a revert into registerBLSPublicKeys()
.
This would partially brick the protocol until the DAO operator used updateWhitelisting()
to set the feature back to false
.
This seems like a clear cut Medium via the rubrick:
Assets not at direct risk, but the function of the protocol or its availability could be impacted[...]
The protocol will not function properly if the whitelisting is enabled and registerBLSPublicKeys()
is called.
Add the following code to LiquidStakingManager.t.sol
:
function testCannotWhitelistNodeRunner() public { address nodeRunner = accountOne; vm.startPrank(admin); //impersonate DAO console.logBool(manager.isNodeRunnerWhitelisted(nodeRunner)); // logs false vm.expectRevert("Unnecessary update to same status"); manager.updateNodeRunnerWhitelistStatus(nodeRunner, true); // reverts vm.stopPrank(); }
The test will pass with a revert. Note that it isn't necessary to enable the whitelist functionality via updateWhitelisting()
first.
Change the check on line 280 to check the state of the mapping against the desired bool supplied by the isWhitelisted
argument.
require(isNodeRunnerWhitelisted[_nodeRunner] != isWhitelisted, "Unnecessary update to same status");
Failing tests: Encountered 1 failing test in test/foundry/LiquidStakingManager.t.sol:LiquidStakingManagerTests [FAIL. Reason: Call did not revert as expected] testCannotWhitelistNodeRunner() (gas: 54248)
It is strongly recommended that all functions of the LiquidStakingManager
contract be covered by the test suite; this issue would have been easily caught with a test.
#0 - c4-judge
2022-11-20T23:56:13Z
dmvt marked the issue as duplicate of #74
#1 - c4-judge
2022-11-21T16:43:20Z
dmvt marked the issue as not a duplicate
#2 - c4-judge
2022-11-21T16:43:28Z
dmvt marked the issue as duplicate of #67
#3 - c4-judge
2022-11-30T11:46:09Z
dmvt marked the issue as satisfactory
#4 - C4-Staff
2022-12-21T00:11:17Z
JeeberC4 marked the issue as duplicate of #378
🌟 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 code inside the function registerBLSPublicKeys()
contains the following comment:
// check if the BLS public key is part of LSD network and is not banned
However, the require
statement immediately following the comment only contains a check for the BLS public key existence check, and not the additional key banned check.
Update the comment if the check isn't required, or add the check the comment indicates should be present.
The function isBLSPublicKeyBanned()
performs two different checks, only one of which is indicated in its name. It does perform a BLS key banned check. It also performs a BLS key registration check. Its later use in stake()
and mintDerivatives()
is such that both checks are required, via code comments. This gives the appearance of mistaken code comments.
Rename the function to be consistent with its implementation and use.
At the indicated line, the require
statement contains the following revert string:
"DAO staking funds vault balance must be at least 4 ether"
However, the actual check is a strict equality for 4 ether
, and not at least 4 ether
.
Change the revert string to correlate with the actual performed check.
The function GiantMevAndFeesPool.batchDepositETHForStaking()
lacks an array length check that other functions in similar contracts do.
Specifically, the check on the array length of _ETHTransactionAmounts
compared to _stakingFundsVault
is missing. The mirrored function in GiantSavETHVaultPool
contains this check, as do nearly all other functions that contain multiple array type arguments.
Add in the missing check to be consistent with other functions in the protocol.
withdrawETH()
The GiantPoolBase
contract provides baseline functions for other contract which inherit it, including GiantMevAndFeesPool
and GiantSavETHVaultPool
.
The function withdrawETH()
is used to burn LP tokens and retrieve ETH from the pool. It contains several checks, among which is the following:
//MIN_STAKING_AMOUNT = 0.001 ether require(_amount >= MIN_STAKING_AMOUNT, "Invalid amount");
Since the contract enforces a minimum deposit, this check seems to be attempting to match the logic on deposit.
However, the logic as implemented permits the user to leave a value of less than the MIN_STAKING_AMOUNT
in the contract.
For example, Alice wishes to participate in the GiantMevAndFeesPool
to earn a portion of fees. She deposits 0.01 ether, well above the minimum amount.
Later, she wishes to withdraw, and calls withdrawETH
with a value of 9500000000000000
, corresponding to 0.0095 ether
. This quantity will pass the require
check as it is larger than MIN_STAKING_AMOUNT
, and Alice's ETH will be withdrawn (assuming the other conditions are met, such as the quantity of idleETH being greater than or equal to her requested amount).
This will leave 0.0005 ETH in the pool. If Alice tries to withdraw the dust, the transaction will fail. It will force her to deposit ETH and perform the withdrawal a second time.
Consider changing the logic to check if the difference of the requested amount would leave less than MIN_STAKING_AMOUNT
in the pool. If so, simply transfer the user's entire balance instead of leaving dust.
Alternatively, perform the same test but revert and require an amount that wouldn't violate MIN_STAKING_AMOUNT
afterwards.
#0 - c4-judge
2022-11-30T14:38:11Z
dmvt marked the issue as grade-b