Skip to content

Commit

Permalink
Merge pull request #33 from aragon/audit
Browse files Browse the repository at this point in the history
Audit
  • Loading branch information
jordaniza authored Oct 3, 2024
2 parents b9ac673 + 31a4da6 commit b13fff5
Show file tree
Hide file tree
Showing 35 changed files with 1,677 additions and 651 deletions.
31 changes: 16 additions & 15 deletions src/clock/Clock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
uint256 internal constant VOTE_DURATION = 1 weeks;

/// @dev Opens and closes the voting window slightly early to avoid timing attacks
uint256 internal constant VOTE_WINDOW_OFFSET = 1 hours;
uint256 internal constant VOTE_WINDOW_BUFFER = 1 hours;

/*///////////////////////////////////////////////////////////////
Initialization
Expand Down Expand Up @@ -54,8 +54,8 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
return VOTE_DURATION;
}

function voteWindowOffset() external pure returns (uint256) {
return VOTE_WINDOW_OFFSET;
function voteWindowBuffer() external pure returns (uint256) {
return VOTE_WINDOW_BUFFER;
}

/*///////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -87,6 +87,7 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
}

/// @notice Number of seconds until the start of the next epoch (relative)
/// @dev If exactly at the start of the epoch, returns 0
function resolveEpochStartsIn(uint256 timestamp) public pure returns (uint256) {
unchecked {
uint256 elapsed = resolveElapsedInEpoch(timestamp);
Expand Down Expand Up @@ -130,15 +131,15 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
uint256 elapsed = resolveElapsedInEpoch(timestamp);

// if less than the offset has past, return the time until the offset
if (elapsed < VOTE_WINDOW_OFFSET) {
return VOTE_WINDOW_OFFSET - elapsed;
if (elapsed < VOTE_WINDOW_BUFFER) {
return VOTE_WINDOW_BUFFER - elapsed;
}
// if voting is active (we are in the voting period) return 0
else if (elapsed < VOTE_DURATION - VOTE_WINDOW_OFFSET) {
else if (elapsed < VOTE_DURATION - VOTE_WINDOW_BUFFER) {
return 0;
}
// else return the time until the next epoch + the offset
else return resolveEpochStartsIn(timestamp) + VOTE_WINDOW_OFFSET;
else return resolveEpochStartsIn(timestamp) + VOTE_WINDOW_BUFFER;
}
}

Expand All @@ -162,7 +163,7 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
function resolveEpochVoteEndsIn(uint256 timestamp) public pure returns (uint256) {
unchecked {
uint256 elapsed = resolveElapsedInEpoch(timestamp);
uint VOTING_WINDOW = VOTE_DURATION - VOTE_WINDOW_OFFSET;
uint VOTING_WINDOW = VOTE_DURATION - VOTE_WINDOW_BUFFER;
// if we are outside the voting period, return 0
if (elapsed >= VOTING_WINDOW) return 0;
// if we are in the voting period, return the remaining time
Expand All @@ -175,7 +176,7 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
}

/// @notice Timestamp of the end of the current voting period (absolute)
function resolveEpochVoteEndTs(uint256 timestamp) internal pure returns (uint256) {
function resolveEpochVoteEndTs(uint256 timestamp) public pure returns (uint256) {
unchecked {
return timestamp + resolveEpochVoteEndsIn(timestamp);
}
Expand All @@ -190,13 +191,13 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
}

/// @notice Number of seconds until the next checkpoint interval (relative)
function resolveEpochNextCheckpointIn(uint256 timestamp) internal pure returns (uint256) {
/// @dev If exactly at the start of the checkpoint interval, returns 0
function resolveEpochNextCheckpointIn(uint256 timestamp) public pure returns (uint256) {
unchecked {
uint256 elapsed = resolveElapsedInEpoch(timestamp);
// elapsed > deposit interval, then subtract the interval
if (elapsed > CHECKPOINT_INTERVAL) elapsed -= CHECKPOINT_INTERVAL;
if (elapsed == 0) return 0;
else return CHECKPOINT_INTERVAL - elapsed;
if (elapsed >= CHECKPOINT_INTERVAL) elapsed -= CHECKPOINT_INTERVAL;
return CHECKPOINT_INTERVAL - elapsed;
}
}

Expand All @@ -205,7 +206,7 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
}

/// @notice Timestamp of the next deposit interval (absolute)
function resolveEpochNextCheckpointTs(uint256 timestamp) internal pure returns (uint256) {
function resolveEpochNextCheckpointTs(uint256 timestamp) public pure returns (uint256) {
unchecked {
return timestamp + resolveEpochNextCheckpointIn(timestamp);
}
Expand All @@ -221,5 +222,5 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
return _getImplementation();
}

uint256[49] private __gap;
uint256[50] private __gap;
}
36 changes: 29 additions & 7 deletions src/clock/IClock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,55 @@ interface IClockUser {
}

interface IClock {
function epochDuration() external pure returns (uint256);

function checkpointInterval() external pure returns (uint256);

function voteDuration() external pure returns (uint256);

function voteWindowBuffer() external pure returns (uint256);

function currentEpoch() external view returns (uint256);

function resolveEpoch(uint256 timestamp) external pure returns (uint256);

function elapsedInEpoch() external view returns (uint256);

function resolveElapsedInEpoch(uint256 timestamp) external pure returns (uint256);

function epochStartsIn() external view returns (uint256);

function resolveEpochStartsIn(uint256 timestamp) external pure returns (uint256);

function epochStartTs() external view returns (uint256);

function resolveEpochStartTs(uint256 timestamp) external pure returns (uint256);

function votingActive() external view returns (bool);

function resolveVotingActive(uint256 timestamp) external pure returns (bool);

function epochVoteStartsIn() external view returns (uint256);

function resolveEpochVoteStartsIn(uint256 timestamp) external pure returns (uint256);

function epochVoteStartTs() external view returns (uint256);

function resolveEpochVoteStartTs(uint256 timestamp) external pure returns (uint256);

function epochVoteEndsIn() external view returns (uint256);

function epochVoteEndTs() external view returns (uint256);
function resolveEpochVoteEndsIn(uint256 timestamp) external pure returns (uint256);

function epochNextCheckpointIn() external view returns (uint256);
function epochVoteEndTs() external view returns (uint256);

function epochNextCheckpointTs() external view returns (uint256);
function resolveEpochVoteEndTs(uint256 timestamp) external pure returns (uint256);

function epochDuration() external pure returns (uint256);
function epochNextCheckpointIn() external view returns (uint256);

function checkpointInterval() external pure returns (uint256);
function resolveEpochNextCheckpointIn(uint256 timestamp) external pure returns (uint256);

function voteDuration() external pure returns (uint256);
function epochNextCheckpointTs() external view returns (uint256);

function voteWindowOffset() external pure returns (uint256);
function resolveEpochNextCheckpointTs(uint256 timestamp) external pure returns (uint256);
}
44 changes: 24 additions & 20 deletions src/escrow/increasing/ExitQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,23 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
/// @notice role required to withdraw tokens from the escrow contract
bytes32 public constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE");

/// @dev 10_000 = 100%
uint16 private constant MAX_FEE_PERCENT = 10_000;

/// @notice the fee percent charged on withdrawals
uint256 public feePercent;

/// @notice address of the escrow contract
address public escrow;

/// @notice clock contract for epoch duration
address public clock;

/// @notice time in seconds between exit and withdrawal
uint256 public cooldown;

/// @notice the fee percent charged on withdrawals
/// @dev 1e18 = 100%
uint256 public feePercent;
uint48 public cooldown;

/// @notice minimum time from the original lock date before one can enter the queue
uint256 public minLock;
uint48 public minLock;

/// @notice tokenId => Ticket
mapping(uint256 => Ticket) internal _queue;
Expand All @@ -55,11 +57,11 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
/// @param _dao address of the DAO that will be able to set the queue
function initialize(
address _escrow,
uint256 _cooldown,
uint48 _cooldown,
address _dao,
uint256 _feePercent,
address _clock,
uint256 _minLock
uint48 _minLock
) external initializer {
__DaoAuthorizableUpgradeable_init(IDAO(_dao));
escrow = _escrow;
Expand All @@ -84,11 +86,11 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {

/// @notice The exit queue manager can set the cooldown period
/// @param _cooldown time in seconds between exit and withdrawal
function setCooldown(uint256 _cooldown) external auth(QUEUE_ADMIN_ROLE) {
function setCooldown(uint48 _cooldown) external auth(QUEUE_ADMIN_ROLE) {
_setCooldown(_cooldown);
}

function _setCooldown(uint256 _cooldown) internal {
function _setCooldown(uint48 _cooldown) internal {
cooldown = _cooldown;
emit CooldownSet(_cooldown);
}
Expand All @@ -100,18 +102,20 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
}

function _setFeePercent(uint256 _feePercent) internal {
if (_feePercent > 1e18) revert FeeTooHigh();
if (_feePercent > MAX_FEE_PERCENT) revert FeeTooHigh(MAX_FEE_PERCENT);
feePercent = _feePercent;
emit FeePercentSet(_feePercent);
}

/// @notice The exit queue manager can set the minimum lock time
/// @param _minLock the minimum time from the original lock date before one can enter the queue
function setMinLock(uint256 _minLock) external auth(QUEUE_ADMIN_ROLE) {
/// @dev Min 1 second to prevent single block deposit-withdrawal attacks
function setMinLock(uint48 _minLock) external auth(QUEUE_ADMIN_ROLE) {
_setMinLock(_minLock);
}

function _setMinLock(uint256 _minLock) internal {
function _setMinLock(uint48 _minLock) internal {
if (_minLock == 0) revert MinLockOutOfBounds();
minLock = _minLock;
emit MinLockSet(_minLock);
}
Expand Down Expand Up @@ -142,7 +146,7 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
if (_queue[_tokenId].holder != address(0)) revert AlreadyQueued();

// get time to min lock and revert if it hasn't been reached
uint minLockTime = timeToMinLock(_tokenId);
uint48 minLockTime = timeToMinLock(_tokenId);
if (minLockTime > block.timestamp) revert MinLockNotReached(_tokenId, minLock, minLockTime);

uint exitDate = nextExitDate();
Expand All @@ -159,7 +163,7 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
uint cooldownExpiry = block.timestamp + cooldown;

// if the next cp is after the cooldown, return the next cp
return nextCP >= cooldownExpiry ? nextCP : cooldownExpiry;
return nextCP > cooldownExpiry ? nextCP : cooldownExpiry;
}

/// @notice Exits the queue for that tokenID.
Expand All @@ -180,7 +184,7 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
if (feePercent == 0) return 0;
uint underlyingBalance = IVotingEscrow(escrow).locked(_tokenId).amount;
if (underlyingBalance == 0) revert NoLockBalance();
return (underlyingBalance * feePercent) / 1e18;
return (underlyingBalance * feePercent) / MAX_FEE_PERCENT;
}

/*//////////////////////////////////////////////////////////////
Expand All @@ -192,7 +196,7 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
function canExit(uint256 _tokenId) public view returns (bool) {
Ticket memory ticket = _queue[_tokenId];
if (ticket.holder == address(0)) return false;
return block.timestamp >= ticket.exitDate;
return block.timestamp > ticket.exitDate;
}

/// @return holder of a ticket for a given tokenId
Expand All @@ -204,8 +208,8 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
return _queue[_tokenId];
}

function timeToMinLock(uint256 _tokenId) public view returns (uint256) {
uint256 lockStart = IVotingEscrow(escrow).locked(_tokenId).start;
function timeToMinLock(uint256 _tokenId) public view returns (uint48) {
uint48 lockStart = IVotingEscrow(escrow).locked(_tokenId).start;
return lockStart + minLock;
}

Expand All @@ -222,5 +226,5 @@ contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
/// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)).
function _authorizeUpgrade(address) internal virtual override auth(QUEUE_ADMIN_ROLE) {}

uint256[44] private __gap;
uint256[46] private __gap;
}
Loading

0 comments on commit b13fff5

Please sign in to comment.