Skip to content

Commit

Permalink
feature: clock
Browse files Browse the repository at this point in the history
  • Loading branch information
jordaniza committed Sep 12, 2024
1 parent 81c12cb commit 547ee14
Show file tree
Hide file tree
Showing 27 changed files with 485 additions and 240 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ The main workflow in the Mode Governance build is as follows:
- Custom exit queue logic via custom exit queue managers
- Custom escrow curves
- Custom voting contracts other than SimpleGaugeVoter
- Custom epoch clock logic via the `Clock.sol` contract

- Additionally, we use libraries like `EpochDurationLib`, `CurveCoefficientLib` and `SignedFixedPointMathLib` that allow users to make minimal, consistent and gas-efficient customisations to things like epoch length and curve shapes.
- Additionally, we use libraries like `CurveCoefficientLib` and `SignedFixedPointMathLib` that allow users to make minimal, consistent and gas-efficient customisations to things like epoch length and curve shapes.

## Rewards

Expand Down
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ utils/=test/utils/
@execution-chain/=src/execution-chain/
@utils/=src/utils/
@helpers/=test/helpers/
@clock/=src/clock/

@ensdomains/ens-contracts/=lib/ens-contracts/
ens-contracts/=lib/ens-contracts/contracts/
Expand Down
Empty file removed script/Deploy.s.sol
Empty file.
225 changes: 225 additions & 0 deletions src/clock/Clock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// interfaces
import {IDAO} from "@aragon/osx/core/dao/IDAO.sol";
import {IClock} from "./IClock.sol";

// contracts
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {DaoAuthorizableUpgradeable as DaoAuthorizable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol";

/// @title Clock
contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable {
bytes32 public constant CLOCK_ADMIN_ROLE = keccak256("CLOCK_ADMIN_ROLE");

/// @dev Epoch encompasses a voting and non-voting period
uint256 internal constant EPOCH_DURATION = 2 weeks;

/// @dev Checkpoint interval is the time between each voting checkpoint
uint256 internal constant CHECKPOINT_INTERVAL = 1 weeks;

/// @dev Voting duration is the time during which votes can be cast
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;

/*///////////////////////////////////////////////////////////////
Initialization
//////////////////////////////////////////////////////////////*/

constructor() {
_disableInitializers();
}

function initialize(address _dao) external initializer {
__DaoAuthorizableUpgradeable_init(IDAO(_dao));
// uups not needdd
}

/*///////////////////////////////////////////////////////////////
Getters
//////////////////////////////////////////////////////////////*/

function epochDuration() external pure returns (uint256) {
return EPOCH_DURATION;
}

function checkpointInterval() external pure returns (uint256) {
return CHECKPOINT_INTERVAL;
}

function voteDuration() external pure returns (uint256) {
return VOTE_DURATION;
}

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

/*///////////////////////////////////////////////////////////////
Epochs
//////////////////////////////////////////////////////////////*/

function currentEpoch() external view returns (uint256) {
return resolveEpoch(block.timestamp);
}

function resolveEpoch(uint256 timestamp) public pure returns (uint256) {
unchecked {
return timestamp / EPOCH_DURATION;
}
}

function elapsedInEpoch() external view returns (uint256) {
return resolveElapsedInEpoch(block.timestamp);
}

function resolveElapsedInEpoch(uint256 timestamp) public pure returns (uint256) {
unchecked {
return timestamp % EPOCH_DURATION;
}
}

function epochStartsIn() external view returns (uint256) {
return resolveEpochStartsIn(block.timestamp);
}

/// @notice Number of seconds until the start of the next epoch (relative)
function resolveEpochStartsIn(uint256 timestamp) public pure returns (uint256) {
unchecked {
uint256 elapsed = resolveElapsedInEpoch(timestamp);
return (elapsed == 0) ? 0 : EPOCH_DURATION - elapsed;
}
}

function epochStartTs() external view returns (uint256) {
return resolveEpochStartTs(block.timestamp);
}

/// @notice Timestamp of the start of the next epoch (absolute)
function resolveEpochStartTs(uint256 timestamp) public pure returns (uint256) {
unchecked {
return timestamp + resolveEpochStartsIn(timestamp);
}
}

/*///////////////////////////////////////////////////////////////
Voting
//////////////////////////////////////////////////////////////*/

function votingActive() external view returns (bool) {
return resolveVotingActive(block.timestamp);
}

function resolveVotingActive(uint256 timestamp) public pure returns (bool) {
bool afterVoteStart = timestamp >= resolveEpochVoteStartTs(timestamp);
bool beforeVoteEnd = timestamp < resolveEpochVoteEndTs(timestamp);
return afterVoteStart && beforeVoteEnd;
}

function epochVoteStartsIn() external view returns (uint256) {
return resolveEpochVoteStartsIn(block.timestamp);
}

/// @notice Number of seconds until voting starts.
/// @dev If voting is active, returns 0.
function resolveEpochVoteStartsIn(uint256 timestamp) public pure returns (uint256) {
unchecked {
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 voting is active (we are in the voting period) return 0
else if (elapsed < VOTE_DURATION - VOTE_WINDOW_OFFSET) {
return 0;
}
// else return the time until the next epoch + the offset
else return resolveEpochStartsIn(timestamp) + VOTE_WINDOW_OFFSET;
}
}

function epochVoteStartTs() external view returns (uint256) {
return resolveEpochVoteStartTs(block.timestamp);
}

/// @notice Timestamp of the start of the next voting period (absolute)
function resolveEpochVoteStartTs(uint256 timestamp) public pure returns (uint256) {
unchecked {
return timestamp + resolveEpochVoteStartsIn(timestamp);
}
}

function epochVoteEndsIn() external view returns (uint256) {
return resolveEpochVoteEndsIn(block.timestamp);
}

/// @notice Number of seconds until the end of the current voting period (relative)
/// @dev If we are outside the voting period, returns 0
function resolveEpochVoteEndsIn(uint256 timestamp) public pure returns (uint256) {
unchecked {
uint256 elapsed = resolveElapsedInEpoch(timestamp);
uint VOTING_WINDOW = VOTE_DURATION - VOTE_WINDOW_OFFSET;
// 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
else return VOTING_WINDOW - elapsed;
}
}

function epochVoteEndTs() external view returns (uint256) {
return resolveEpochVoteEndTs(block.timestamp);
}

/// @notice Timestamp of the end of the current voting period (absolute)
function resolveEpochVoteEndTs(uint256 timestamp) internal pure returns (uint256) {
unchecked {
return timestamp + resolveEpochVoteEndsIn(timestamp);
}
}

/*///////////////////////////////////////////////////////////////
Checkpointing
//////////////////////////////////////////////////////////////*/

function epochNextCheckpointIn() external view returns (uint256) {
return resolveEpochNextCheckpointIn(block.timestamp);
}

/// @notice Number of seconds until the next checkpoint interval (relative)
function resolveEpochNextCheckpointIn(uint256 timestamp) internal 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;
}
}

function epochNextCheckpointTs() external view returns (uint256) {
return resolveEpochNextCheckpointTs(block.timestamp);
}

/// @notice Timestamp of the next deposit interval (absolute)
function resolveEpochNextCheckpointTs(uint256 timestamp) internal pure returns (uint256) {
unchecked {
return timestamp + resolveEpochNextCheckpointIn(timestamp);
}
}

/*///////////////////////////////////////////////////////////////
UUPS Getters
//////////////////////////////////////////////////////////////*/

function _authorizeUpgrade(address) internal override auth(CLOCK_ADMIN_ROLE) {}

function implementation() external view returns (address) {
return _getImplementation();
}

uint256[49] private __gap;
}
38 changes: 38 additions & 0 deletions src/clock/IClock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IClockUser {
function clock() external view returns (address);
}

interface IClock {
function currentEpoch() external view returns (uint256);

function elapsedInEpoch() external view returns (uint256);

function epochStartsIn() external view returns (uint256);

function epochStartTs() external view returns (uint256);

function votingActive() external view returns (bool);

function epochVoteStartsIn() external view returns (uint256);

function epochVoteStartTs() external view returns (uint256);

function epochVoteEndsIn() external view returns (uint256);

function epochVoteEndTs() external view returns (uint256);

function epochNextCheckpointIn() external view returns (uint256);

function epochNextCheckpointTs() external view returns (uint256);

function epochDuration() external pure returns (uint256);

function checkpointInterval() external pure returns (uint256);

function voteDuration() external pure returns (uint256);

function voteWindowOffset() external pure returns (uint256);
}
17 changes: 11 additions & 6 deletions src/escrow/increasing/ExitQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import {IDAO} from "@aragon/osx/core/dao/IDAO.sol";
import {IExitQueue} from "./interfaces/IExitQueue.sol";
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {IVotingEscrowIncreasing as IVotingEscrow} from "@escrow-interfaces/IVotingEscrowIncreasing.sol";
import {IClockUser, IClock} from "@clock/IClock.sol";

import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {EpochDurationLib} from "@libs/EpochDurationLib.sol";

import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {DaoAuthorizableUpgradeable as DaoAuthorizable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol";

/// @title ExitQueue
/// @notice Token IDs associated with an NFT are given a ticket when they are queued for exit.
/// After a cooldown period, the ticket holder can exit the NFT.
contract ExitQueue is IExitQueue, DaoAuthorizable, UUPSUpgradeable {
contract ExitQueue is IExitQueue, IClockUser, DaoAuthorizable, UUPSUpgradeable {
using SafeERC20 for IERC20;

/// @notice role required to manage the exit queue
Expand All @@ -27,6 +27,9 @@ contract ExitQueue is IExitQueue, DaoAuthorizable, UUPSUpgradeable {
/// @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;

Expand All @@ -37,8 +40,6 @@ contract ExitQueue is IExitQueue, DaoAuthorizable, UUPSUpgradeable {
/// @notice tokenId => Ticket
mapping(uint256 => Ticket) internal _queue;

uint256[46] private __gap;

/*//////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/
Expand All @@ -53,10 +54,12 @@ contract ExitQueue is IExitQueue, DaoAuthorizable, UUPSUpgradeable {
address _escrow,
uint256 _cooldown,
address _dao,
uint256 _feePercent
uint256 _feePercent,
address _clock
) external initializer {
__DaoAuthorizableUpgradeable_init(IDAO(_dao));
escrow = _escrow;
clock = _clock;
_setFeePercent(_feePercent);
_setCooldown(_cooldown);
}
Expand Down Expand Up @@ -132,7 +135,7 @@ contract ExitQueue is IExitQueue, DaoAuthorizable, UUPSUpgradeable {
/// @dev The next exit date is the later of the cooldown expiry and the next checkpoint
function nextExitDate() public view returns (uint256) {
// snap to next checkpoint interval, we can't cooldown before this
uint nextCP = EpochDurationLib.epochNextCheckpointTs(block.timestamp);
uint nextCP = IClock(clock).epochNextCheckpointTs();
uint cooldownExpiry = block.timestamp + cooldown;

// if the next cp is after the cooldown, return the next cp
Expand Down Expand Up @@ -193,4 +196,6 @@ contract ExitQueue is IExitQueue, 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[45] private __gap;
}
Loading

0 comments on commit 547ee14

Please sign in to comment.