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: 45/111
Findings: 3
Award: $282.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: imare
Also found by: 0Kage, 0xbepresent, AkshaySrivastav, Faith, HollaDieWaldfee, Jeiwan, Saintcode_, betweenETHlines, btk, dic0de, enckrish, gz627, imare, jadezti, kaliberpoziomka8552, nogo, simon135, sk8erboy
34.7487 USDC - $34.75
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
MultisigManager.sol
function requireNextActiveMultisig()
will always return the first enabled multisig address instead of the next one.
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.
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; } }
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)
🌟 Selected for report: bin2chen
Also found by: 0xbepresent, 0xhunter, RaymondFam, Saintcode_, adriro, ast3ros, cryptonue, immeas
179.3848 USDC - $179.38
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
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
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
🌟 Selected for report: HollaDieWaldfee
Also found by: Allarious, Aymen0909, Saintcode_, SmartSek, adriro, bin2chen, cccz, kaliberpoziomka8552, minhtrng, rvierdiiev, sces60107, sk8erboy, wagmi
68.0946 USDC - $68.09
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
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.
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