GoGoPool contest - Saintcode_'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: 45/111

Findings: 3

Award: $282.22

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

34.7487 USDC - $34.75

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-702

External Links

Lines of code

https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MultisigManager.sol#L80-L91 https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L236

Vulnerability details

MultisigManager.sol function requireNextActiveMultisig() will always return the first enabled multisig address instead of the next one.

Impact

Only the first registered multisig address is always assigned to a pool, making the rest of the registered multisig addresses useless. And therefore less decentralized.

Proof of Concept

https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L236 createMinipool() function on MiniPoolManager.sol calls multisigManager.requireNextActiveMultisig() to assign a new multisig to the current pool.

https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MultisigManager.sol#L80-L91 But the requireNextActiveMultisig() function on MultisigManager.sol always returns the first enabled multisig address, as it traverses all multisigs by index from 0 to count and returns the first enabled one instead of the next one:

for (uint256 i = 0; i < total; i++) { (addr, enabled) = getMultisig(i); if (enabled) { return addr; } }

Tools Used

vscode, Foundry

If the protocol intends to get the next multisig address as the function name requireNextActiveMultisig() says, a good fix would be to keep track of the last assigned multisig address on MinipoolManager and start traversing all multi sig from that index instead of from position 0.

#0 - c4-judge

2023-01-08T13:26:11Z

GalloDaSballo marked the issue as duplicate of #702

#1 - c4-judge

2023-02-08T19:58:19Z

GalloDaSballo marked the issue as satisfactory

#2 - c4-judge

2023-02-08T19:58:33Z

GalloDaSballo changed the severity to 2 (Med Risk)

Findings Information

🌟 Selected for report: bin2chen

Also found by: 0xbepresent, 0xhunter, RaymondFam, Saintcode_, adriro, ast3ros, cryptonue, immeas

Labels

bug
2 (Med Risk)
satisfactory
duplicate-521

Awards

179.3848 USDC - $179.38

External Links

Lines of code

https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MultisigManager.sol#L17 https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MultisigManager.sol#L27

Vulnerability details

It is not possible to change, unregister or replace a multisig address from the protocol, it is only possible to disable a multisig. What limits the protocol to the same 10 multisig addresses forever

Impact

Once the MULTISIG_LIMIT is reached, there is no way of upgrading a multisig or even unregistering it which makes the protocol bound to the same 10 addresses forever, therefore if there's a need to upgrade them, the MultisigManager would have to be redeployed with new logic to permit multisig replacement.

Implement a way to unregister or change a current multisig by index, following registerMultisig() implementation but adding an index parameter. A possible implementation would be:

function changeMultisig(address addr, uint256 index) external onlyGuardian { uint256 currentIndex = getUint(keccak256("multisig.count")); if (index > currentIndex ) { revert MultisigDoesNotExist(); } setAddress(keccak256(abi.encodePacked("multisig.item", index, ".address")), addr); setUint(keccak256(abi.encodePacked("multisig.index", addr)), index + 1); emit ChangedMultisig(addr, msg.sender); }

#0 - c4-judge

2023-01-09T18:59:04Z

GalloDaSballo marked the issue as duplicate of #521

#1 - c4-judge

2023-02-08T20:07:20Z

GalloDaSballo marked the issue as satisfactory

Findings Information

Labels

bug
2 (Med Risk)
satisfactory
duplicate-235

Awards

68.0946 USDC - $68.09

External Links

Lines of code

https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L484-L515 https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L528-L533

Vulnerability details

When Multisig calls recordStakingError() to record that a staking error occurred while registering the node as a validator, the MinipoolCount for the owner of the minipool is not decreased. This makes the state of the protocol unstable since a user that goes from the MinipoolStatus.Error state to MinipoolStatus.Finished will still have a MinipoolCount of 1 without having any active pools. And a user that goes from MinipoolStatus.Error to MinipoolStatus.Prelaunch would have a MinipoolCount of 2 even though he only has one active pool.

This incorrect state of the protocol makes a user still eligible for the next cycle of rewards without having an active staking Minipool.

Functions _cancelMinipoolAndReturnFunds() and recordStakingEnd() call staking.decreaseMinipoolCount(owner) to decrease Minipoolcount when a pool is no longer staking: https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L435-L437

Staking staking = Staking(getContractAddress("Staking")); staking.decreaseAVAXAssigned(owner, avaxLiquidStakerAmt); staking.decreaseMinipoolCount(owner);

But neither recordStakingError() nor finishFailedMinipoolByMultisig() call that function to decrease Minipoolcount when a pool is no longer staking. https://github.com/code-423n4/2022-12-gogopool/blob/aec9928d8bdce8a5a4efe45f54c39d4fc7313731/contracts/contract/MinipoolManager.sol#L509-L510

Staking staking = Staking(getContractAddress("Staking")); staking.decreaseAVAXAssigned(owner, avaxLiquidStakerAmt);

It is recommended to call staking.decreaseMinipoolCount(owner) on the recordStakingError() function right after decreasing AVAX assigned in order to maintain a correct count of active staking pools and protect the system against rewarding a user that's not longer staking.

Tools Used

vscode, Foundry

#0 - 0xminty

2023-01-04T00:40:05Z

dupe of #235

#1 - c4-judge

2023-01-09T09:42:28Z

GalloDaSballo marked the issue as duplicate of #235

#2 - c4-judge

2023-02-08T19:41:22Z

GalloDaSballo marked the issue as satisfactory

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