Summary
veRAACToken is pausable in that it has a public paused
variable and uses the standard whenNotPaused
modifiers. However there is no way to set paused = true
.
Finding Description
veRAACToken.sol includes a public state variable for paused
and a standard modifier using it:
* @notice Whether the contract is paused
*/
bool public paused;
* @notice Modifier to check if the contract is not paused
*/
modifier whenNotPaused() {
if (paused) revert ContractPaused();
_;
}
Source: veRAACToken.sol#L100-L103 and veRAACToken.sol#L150-L156
The whenNotPaused
modifier is used by the functions lock
, increase
, and extend
. e.g.
function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
Source: veRAACToken.sol#L212
However there is no function to assign a value to paused
!
We expect an implementation such as this one from LendingPool.sol:
* @notice Pauses the contract functions under `whenNotPaused`
* @dev Only callable by the contract owner
*/
function pause() external onlyOwner {
_pause();
}
* @notice Unpauses the contract functions under `whenNotPaused`
* @dev Only callable by the contract owner
*/
function unpause() external onlyOwner {
_unpause();
}
Source: LendingPool.sol#L659-L673
Impact Explanation
In the event of an emergency, pausing the contract is NOT an option for the team. Given the partial implementation of the feature in this token's contract and then fact other contracts in this protocol do implement pause correctly suggests that the team believes this emergency action would be available to them.
Likelihood Explanation
Ideally pause
is never needed. However contracts often includes it just in case, hoping that it never needs to be called.
Proof of Concept
It's difficult to prove that a needed capability is missing, but we can search for "paused =" to confirm veRAACToken doesn't include any functions for this purpose. Additionally we can cast interface veRAACToken
which helps ensure we consider inherited contracts as well, after removing the view only functions we get the following interface. A quick scan also confirms ("emergency" functions here do not use the paused variable).
interface veRAACToken {
function approve(address spender, uint256 value) external returns (bool);
function cancelEmergencyAction(bytes32 actionId) external;
function emergencyWithdraw() external;
function enableEmergencyWithdraw() external;
function executeEmergencyUnlock() external;
function extend(uint256 newDuration) external;
function increase(uint256 amount) external;
function lock(uint256 amount, uint256 duration) external;
function recordVote(address voter, uint256 proposalId) external;
function renounceOwnership() external;
function scheduleEmergencyAction(bytes32 actionId) external;
function scheduleEmergencyUnlock() external;
function setMinter(address _minter) external;
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function transferOwnership(address newOwner) external;
function withdraw() external;
}
Recommendation
Add functions to pause/unpause like other contracts such as the LendingPool currently do. This git patch adds simple functions for this purpose:
@@ -203,6 +203,14 @@ contract veRAACToken is ERC20, Ownable, ReentrancyGuard, IveRAACToken {
_lockState.maxTotalLocked = MAX_TOTAL_LOCKED_AMOUNT; // 1B
}
+ function pause() external onlyOwner {
+ paused = true;
+ }
+
+ function unpause() external onlyOwner {
+ paused = false;
+ }
+
/**
* @notice Creates a new lock position for RAAC tokens
* @dev Locks RAAC tokens for a specified duration and mints veRAAC tokens representing voting power