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: 40/111
Findings: 2
Award: $408.47
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: HollaDieWaldfee
Also found by: 0xdeadbeef0x, bin2chen, cozzetti, danyams, enckrish, imare, ladboy233
373.7183 USDC - $373.72
https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MinipoolManager.sol#L670
Multisigs can update the expected AVAX reward rate to adjust for the staking reward rate provided by AVAX staking. If the expected rate is increased, then a larger amount of GGP of validators will be slashed if they don't provide any reward. But, if validators don't have enough GGP staked (GGP staked is fixed at 10% of AVAX assigned), to account for the expected reward rate, multiplied with the duration, then slash
will revert. This will result in the particular pool not being able to be withdrawable, and the liquid staker and node operator funds will be stuck.
uint256 expectedAVAXRewardsAmt = getExpectedAVAXRewardsAmt(duration, avaxLiquidStakerAmt); uint256 slashGGPAmt = calculateGGPSlashAmt(expectedAVAXRewardsAmt); setUint(keccak256(abi.encodePacked("minipool.item", index, ".ggpSlashAmt")), slashGGPAmt); emit GGPSlashed(nodeID, slashGGPAmt); Staking staking = Staking(getContractAddress("Staking")); staking.slashGGP(owner, slashGGPAmt);
While it is possible to call recordStakingEnd
at an earlier time than the expected duration, it will still result in slashing for the entire duration, so it is not a valid path.
Bob creates a minipool with 1000 AVAX and 100 AVAX worth of GGP staked.
Sometime later, in responding to higher AVAX staking rates, a multisig increases the expected AVAX rewards rate.
Since, Bob has high downtime, he doesn't get any reward, and the multisig calls MinipoolManager.recordStakingEnd
with 0 avaxTotalRewardAmt
. When slash
is called, it reverts expectedAVAXRewardsAmt > X
. This also causes recordStakingEnd
to revert, and the funds are not able to be returned.
Manual inspection
slash
should slash based on the current time and the start time of the pool. Alternatively, the multisig can send an end time, which will be used to calculate the amount to be slashed.
Also, if the amount to be slashed is greater than the staked amount, it should slash the staked amount only, to prevent panic due to underflow.
#0 - GalloDaSballo
2023-01-10T19:24:36Z
Arguably dup of big duration to skip slashing, but keeping separate for now
#1 - c4-sponsor
2023-01-11T19:08:43Z
emersoncloud marked the issue as sponsor confirmed
#2 - emersoncloud
2023-01-11T19:14:49Z
Only the Rialto multisig can set the expectedAVAXRewardsAmt
, so we can be sure to take precautions while setting. But slashing should not revert for underflow, we should handle that case.
#3 - 0xju1ie
2023-01-20T11:44:55Z
would agree its mostly a dupe of #694 but with the added revert for underflow
#4 - emersoncloud
2023-01-24T13:07:53Z
Duplicate of #136
#5 - GalloDaSballo
2023-02-01T19:32:24Z
In contrast to #136 this report focuses mostly on admin privilege, while still hitting at the core issue of reverting
I'm awarding 50% for this reason
#6 - c4-judge
2023-02-01T19:32:33Z
GalloDaSballo marked the issue as duplicate of #136
#7 - c4-judge
2023-02-01T19:32:38Z
GalloDaSballo marked the issue as partial-50
#8 - c4-judge
2023-02-03T19:40:44Z
GalloDaSballo changed the severity to 3 (High Risk)
🌟 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/main/contracts/contract/MinipoolManager.sol#L236 https://github.com/code-423n4/2022-12-gogopool/blob/main/contracts/contract/MultisigManager.sol#L67
All pools get assigned to the same multisig, assuming the protocol runs at least 1 active pool every time.
Every minipool created is assigned a multisig, which cannot be changed later. The protocol selects a multisig for this using MinipoolManager.requireNextActiveMultisig
, which returns the first enabled multisig.
Filename: contracts/contract/MinipoolManager.sol 236: address multisig = multisigManager.requireNextActiveMultisig();
To configure, such that any new pool gets assigned a new multisig, the previous has to be disabled. But comments indicate that disabling a multisig, will also disable validations by any pools that have that multisig.
Filename: contracts/contract/MultisigManager.sol 67: /// @dev this will prevent the multisig from completing validations. The minipool will need to be manually reassigned to a new multisig 68: function disableMultisig(address addr) external guardianOrSpecificRegisteredContract("Ocyticus", msg.sender) {
The code doesn't contain any function to reassign a pool to a new multisig. So, if the protocol always has at least 1 minipool running, it becomes impossible to change the multisig.
Creating a new pool will assign to it the first enables multisig. Any new pool created from when this pool started to when this pool ends, will have the same multisig, since disabling that multisig to change to another, will disable validation by the earlier pool. So, assuming the protocol always has 1 pool active, that multisig could never be changed.
Manual inspection
Add a function to reassign all pools of a to-be-disabled multisig to a different multisig. This may include logic to transfer the staking funds plus any rewards, from the old multisig to the new multisig, through the reassigning function.
#0 - c4-judge
2023-01-08T13:26:12Z
GalloDaSballo marked the issue as duplicate of #702
#1 - c4-judge
2023-02-08T19:58:15Z
GalloDaSballo marked the issue as satisfactory