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
Rank: 75/111
Findings: 2
Award: $67.13
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xdeadbeef0x
Also found by: 0Kage, 0xc0ffEE, 0xmint, AkshaySrivastav, Allarious, Ch_301, Franfran, HollaDieWaldfee, IllIllI, Jeiwan, Lirios, Manboy, adriro, ak1, bin2chen, caventa, chaduke, clems4ever, cozzetti, datapunk, imare, immeas, kaliberpoziomka8552, ladboy233, pauliax, peritoflores, rvierdiiev, sces60107, sk8erboy, stealthyz, unforgiven, wagmi, wallstreetvilkas, yixxas
9.9345 USDC - $9.93
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L239-L246 https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L163-L164
If a minipool is in the Withdrawable
or Error
state (MinipoolStatus
), anyone can create a new minipool with the same nodeID
(using the createMinipool
function). This will prevent the original minipool owner from withdrawing their funds (via withdrawMinipoolFunds
) since they will no longer be the owner. The funds will be locked in the contract.
At line 206 in MinipoolManagerTest
(in the testWithdrawMinipoolFunds
function), add:
address hacker = getActorWithTokens("hacker", MAX_AMT, MAX_AMT); vm.startPrank(hacker); ggp.approve(address(staking), MAX_AMT); staking.stakeGGP(ggpStakeAmt); uint256 delegationFee = 0.02 ether; minipoolMgr.createMinipool{value: depositAmt}(mp1.nodeID, 2 weeks, delegationFee, 1000 ether); vm.stopPrank();
This code snippet will create a new minipool with the same nodeID as nodeOp
's minipool and make the nodeOp
's subsequent withdrawal attempt fail.
Foundry
In the requireValidStateTransition
function, the transition from Withdrawable
or Error
states to the Prelaunch
state should not be valid.
#0 - 0xminty
2023-01-04T00:12:53Z
dupe of #805
#1 - c4-judge
2023-01-09T12:37:46Z
GalloDaSballo marked the issue as duplicate of #213
#2 - c4-judge
2023-02-03T12:33:01Z
GalloDaSballo changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-02-03T19:26:10Z
GalloDaSballo changed the severity to 3 (High Risk)
#4 - c4-judge
2023-02-08T08:26:45Z
GalloDaSballo changed the severity to 2 (Med Risk)
#5 - c4-judge
2023-02-08T08:50:12Z
GalloDaSballo changed the severity to 3 (High Risk)
#6 - c4-judge
2023-02-08T20:30:04Z
GalloDaSballo marked the issue as satisfactory
#7 - c4-judge
2023-02-09T08:53:06Z
GalloDaSballo changed the severity to QA (Quality Assurance)
#8 - Simon-Busch
2023-02-09T12:51:40Z
Changed severity back from QA to H as requested by @GalloDaSballo
🌟 Selected for report: 0xbepresent
Also found by: 0Kage, Atarpara, Ch_301, Manboy, cozzetti, datapunk, immeas, kaliberpoziomka8552, peritoflores, rvierdiiev, sces60107, unforgiven, wagmi, yixxas
57.1995 USDC - $57.20
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L528-L533 https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L290
After the finishFailedMinipoolByMultisig
function is called (by a valid mutisig) on a minipool in the Error
or Withdrawable
state, the node operator will no longer be able to withdraw (via the withdrawMinipoolFunds
function) from that minipool.
This is because the finishFailedMinipoolByMultisig
function will set the minipool into the Finished
state, and in the withdrawMinipoolFunds
function, the call to the requireValidStateTransition
function will fail, as the transitioning from the Finished
state to the Finished
state is not a valid state transition.
Also note that the finishFailedMinipoolByMultisig
function does not check that the minipool is in the Error
state, so it can also be called on a minipool in the Withdrawable
state, as Withdrawable
-> Finished
is a valid state transition. Preventing the node operator from withdrawing.
After line 577 in test/unit/MinipoolManager.t.sol
(at the end of the testRecordStakingError
function), add this code snippet:
// the nodeOp tries to withdraw after the minipool went into the `Error` state, and the `finishFailedMinipoolByMultisig` // function was called by the multisig vm.prank(nodeOp); minipoolMgr.withdrawMinipoolFunds(mp1.nodeID);
This will fail with an InvalidStateTransition
error, as the state change from Finished
to Finished
in the withdrawMinipoolFunds
function fails.
Foundry
Remove the finishFailedMinipoolByMultisig
function.
If required, add a makeWithdrawableFailedMinipoolByMultisig
, that lets the multisig transition the minipool state from the Error
to then Withdrawable
state. This will still allow the node operator to withdraw.
#0 - c4-judge
2023-01-10T10:00:18Z
GalloDaSballo marked the issue as duplicate of #175
#1 - c4-judge
2023-01-29T15:28:54Z
GalloDaSballo marked the issue as duplicate of #723
#2 - c4-judge
2023-02-08T20:13:56Z
GalloDaSballo marked the issue as satisfactory