fastTrackProposalExecution() is used to execute specific proposal while TemporalGovernor is paused. There is invariant in contract that guardian must be not allowed to pause when contract is paused.
However this invariant can be broken unintentionally. And the only way to recover in this situation is to revoke guardian. Then protocol won't be able to execute fastTrackProposals and to pause governance.
/// There are a few assumptions that are made in this contract:
/// 1. Wormhole is secure and will not send malicious messages or be deactivated.
/// 2. Moonbeam is secure.
/// 3. Governance on Moonbeam cannot be compromised.
function togglePause() external onlyOwner {
if (paused()) {
_unpause();
} else {
require(
guardianPauseAllowed,
"TemporalGovernor: guardian pause not allowed"
);
guardianPauseAllowed = false;
lastPauseTime = block.timestamp.toUint248();
_pause();
}
/// statement for SMT solver
assert(!guardianPauseAllowed); /// this should be an unreachable state
}
function grantGuardiansPause() external {
require(
msg.sender == address(this),
"TemporalGovernor: Only this contract can update grant guardian pause"
);
guardianPauseAllowed = true;
lastPauseTime = 0;
emit GuardianPauseGranted(block.timestamp);
}
4) As a result, there was no adequate option left to unpause protocol.
There are 3 ways to call unpause:
1) togglePause() doesn't pass assert statement, because guardianPauseAllowed == true
function togglePause() external onlyOwner {
if (paused()) {
_unpause();
} else {
require(
guardianPauseAllowed,
"TemporalGovernor: guardian pause not allowed"
);
guardianPauseAllowed = false;
lastPauseTime = block.timestamp.toUint248();
_pause();
}
/// statement for SMT solver
assert(!guardianPauseAllowed); /// this should be an unreachable state
}
2) permissionlessUnpause() also doesn't pass assert statement
function permissionlessUnpause() external whenPaused {
require(
lastPauseTime + permissionlessUnpauseTime <= block.timestamp,
"TemporalGovernor: not past pause window"
);
lastPauseTime = 0;
_unpause();
assert(!guardianPauseAllowed); /// this should never revert, statement for SMT solving
emit PermissionlessUnpaused(block.timestamp);
}
3) The only way left is to call revokeGuardian(). But you agree this is not ideal
Lines of code
https://github.com/code-423n4/2023-07-moonwell/blob/fced18035107a345c31c9a9497d0da09105df4df/src/core/Governance/TemporalGovernor.sol#L187-L198
Vulnerability details
Impact
fastTrackProposalExecution()
is used to execute specific proposal whileTemporalGovernor
is paused. There is invariant in contract that guardian must be not allowed to pause when contract is paused. However this invariant can be broken unintentionally. And the only way to recover in this situation is to revoke guardian. Then protocol won't be able to execute fastTrackProposals and to pause governance.Proof of Concept
Let's take a look at the following scenario: 1) Emergency situation occurred, one of these assumptions is broken: https://github.com/code-423n4/2023-07-moonwell/blob/fced18035107a345c31c9a9497d0da09105df4df/src/core/Governance/TemporalGovernor.sol#L16-L19
2) First of all guardian pauses contract, and now
guardianPauseAllowed = false
: https://github.com/code-423n4/2023-07-moonwell/blob/fced18035107a345c31c9a9497d0da09105df4df/src/core/Governance/TemporalGovernor.sol#L270-L2903) Then guardian executes fastTrackProposal, it contains action
grantGuardiansPause()
to allow pausing again. NowguardianPauseAllowed = true
https://github.com/code-423n4/2023-07-moonwell/blob/fced18035107a345c31c9a9497d0da09105df4df/src/core/Governance/TemporalGovernor.sol#L187-L1984) As a result, there was no adequate option left to unpause protocol.
There are 3 ways to call
unpause
: 1) togglePause() doesn't pass assert statement, becauseguardianPauseAllowed == true
2)
permissionlessUnpause()
also doesn't pass assert statement3) The only way left is to call
revokeGuardian()
. But you agree this is not idealTools Used
Manual Review
Recommended Mitigation Steps
Ensure that current state is unpaused in
grantGuardiansPause()
:Assessed type
Governance