Skip to content

Commit

Permalink
Review changes
Browse files Browse the repository at this point in the history
  • Loading branch information
xavikh committed Feb 24, 2025
1 parent 65db70d commit 2598f50
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 102 deletions.
53 changes: 34 additions & 19 deletions src/clock/Clock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable, IClockSeason {
uint256 internal constant VOTE_WINDOW_BUFFER = 1 hours;

/// @dev Seasons array
uint48[] private seasons;
uint48[] private seasonTimestamps;

/*///////////////////////////////////////////////////////////////
Initialization
Expand Down Expand Up @@ -220,49 +220,64 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable, IClockSeason {
Seasons
//////////////////////////////////////////////////////////////*/

/// @notice Returns the current season
/// @dev The startTimestamp of the first season is always 0
/// The endTimestamp of the current season is always 0
function currentSeasonTs() public view returns (uint48 startTimestamp, uint48 endTimestamp) {
return seasonTsAt(uint48(block.timestamp));
}

/// @notice Returns the current season index
/// @dev The season index starts at 0
function currentSeason() external view returns (uint16) {
return IClockSeason(this).seasonAt(uint48(block.timestamp));
function currentSeasonIndex() public view returns (uint16 seasonIndex) {
return seasonIndexAt(uint48(block.timestamp));
}

/// @notice Returns a season's start and end timestamps by index
/// @dev The startTimestamp of the first season is always 0
/// @dev The endTimestamp of the current season is always 0
function season(uint16 seasonIndex) external view returns (uint48 startTimestamp, uint48 endTimestamp) {
if (seasonIndex > seasons.length) {
/// The endTimestamp of the current season is always 0
function seasonTs(uint16 seasonIndex) public view returns (uint48 startTimestamp, uint48 endTimestamp) {
if (seasonIndex > seasonTimestamps.length) {
revert SeasonNotFound();
}
startTimestamp = seasonIndex == 0 ? 0 : seasons[seasonIndex - 1];
endTimestamp = seasonIndex < seasons.length ? seasons[seasonIndex] : 0;
startTimestamp = seasonIndex == 0 ? 0 : seasonTimestamps[seasonIndex - 1];
endTimestamp = seasonIndex < seasonTimestamps.length ? seasonTimestamps[seasonIndex] : 0;
}

/// @notice Returns the season at a given timestamp
/// @dev The startTimestamp of the first season is always 0
/// The endTimestamp of the current season is always 0
function seasonTsAt(uint48 _timestamp) public view returns (uint48 startTimestamp, uint48 endTimestamp) {
uint16 seasonIndex = seasonIndexAt(_timestamp);
return seasonTs(seasonIndex);
}

/// @notice Returns the season index at a given timestamp
/// @dev The season index starts at 0 but the season index as 1 is indexed as 0 in the array
/// @dev If the timestamp is after the last season, returns the length of the seasons array
function seasonAt(uint48 _timestamp) external view returns (uint16) {
for (uint16 i = 0; i < seasons.length; i++) {
if (_timestamp < seasons[i]) {
/// If the timestamp is after the last season, returns the length of the seasons array
function seasonIndexAt(uint48 _timestamp) public view returns (uint16 seasonIndex) {
for (uint16 i = uint16(seasonTimestamps.length); i > 0; i--) {
if (_timestamp >= seasonTimestamps[i - 1]) {
return i;
}
}
return uint16(seasons.length);
return 0;
}

/// @notice Creates a new season
/// @dev The season duration must be greater than EPOCH_DURATION
function newSeason() external auth(CLOCK_ADMIN_ROLE) {
function newSeason() public auth(CLOCK_ADMIN_ROLE) {
uint256 startTime = IClock(this).epochNextCheckpointTs();

if (seasons.length > 0) {
uint48 lastSeason = seasons[seasons.length - 1];
if (seasonTimestamps.length > 0) {
uint48 lastSeason = seasonTimestamps[seasonTimestamps.length - 1];
if (startTime < lastSeason + EPOCH_DURATION) {
revert SeasonTooShort();
}
}
seasons.push(uint48(startTime));
seasonTimestamps.push(uint48(startTime));

emit SeasonStarted(uint16(seasons.length), uint48(startTime));
emit SeasonStarted(uint16(seasonTimestamps.length), uint48(startTime));
}

/*///////////////////////////////////////////////////////////////
Expand All @@ -275,5 +290,5 @@ contract Clock is IClock, DaoAuthorizable, UUPSUpgradeable, IClockSeason {
return _getImplementation();
}

uint256[50] private __gap;
uint256[49] private __gap;
}
10 changes: 7 additions & 3 deletions src/clock/IClock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,17 @@ interface ISeasonErrors {
}

interface IClockSeason is ISeasonEvents, ISeasonErrors {
function currentSeason() external view returns (uint16);
function currentSeasonTs() external view returns (uint48 startTimestamp, uint48 endTimestamp);

function season(
function currentSeasonIndex() external view returns (uint16 seasonIndex);

function seasonTs(
uint16 seasonIndex
) external view returns (uint48 startTimestamp, uint48 endTimestamp);

function seasonAt(uint48 _timestamp) external view returns (uint16 season);
function seasonTsAt(uint48 _timestamp) external view returns (uint48 startTimestamp, uint48 endTimestamp);

function seasonIndexAt(uint48 _timestamp) external view returns (uint16 seasonIndex);

function newSeason() external;
}
3 changes: 1 addition & 2 deletions src/escrow/increasing/QuadraticIncreasingEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,7 @@ contract QuadraticIncreasingEscrow is
if (!_isWarm(lastPoint)) return 0;

// get season at time
uint16 season = IClockSeason(clock).seasonAt(uint48(_t));
(uint48 start, ) = IClockSeason(clock).season(season);
(uint48 start, ) = IClockSeason(clock).seasonTsAt(uint48(_t));

// if the last point is before the season start, use last season start
uint256 timeElapsed;
Expand Down
56 changes: 33 additions & 23 deletions src/voting/SimpleGaugeVoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract SimpleGaugeVoter is
address public clock;

/// @notice season => The total votes that have accumulated in this contract
mapping(uint16 => uint256) public totalVotingPowerCast;
mapping(uint16 => uint256) public seasonTotalVotingPowerCast;

/// @notice enumerable list of all gauges that can be voted on
address[] public gaugeList;
Expand All @@ -36,10 +36,10 @@ contract SimpleGaugeVoter is
mapping(address => Gauge) public gauges;

/// @notice season => gauge => total votes (global)
mapping(uint16 => mapping(address => uint256)) public gaugeVotes;
mapping(uint16 => mapping(address => uint256)) public seasonGaugeVotes;

/// @dev season => tokenId => tokenVoteData
mapping(uint16 => mapping(uint256 => TokenVoteData)) internal tokenVoteData;
mapping(uint16 => mapping(uint256 => TokenVoteData)) internal seasonTokenVoteData;

/*///////////////////////////////////////////////////////////////
Initialization
Expand Down Expand Up @@ -121,8 +121,8 @@ contract SimpleGaugeVoter is
voteData.votes[currentVote.gauge] += votesForGauge;

// update the total weights accruing to this gauge
gaugeVotes[season][currentVote.gauge] += votesForGauge;
totalVotingPowerCast[season] += votesForGauge;
seasonGaugeVotes[season][currentVote.gauge] += votesForGauge;
seasonTotalVotingPowerCast[season] += votesForGauge;
voteData.usedVotingPower += votesForGauge;

emit Voted({
Expand All @@ -131,8 +131,8 @@ contract SimpleGaugeVoter is
epoch: epochId(),
tokenId: _tokenId,
votingPowerCastForGauge: votesForGauge,
totalVotingPowerInGauge: gaugeVotes[season][currentVote.gauge],
totalVotingPowerInContract: totalVotingPowerCast[season],
totalVotingPowerInGauge: seasonGaugeVotes[season][currentVote.gauge],
totalVotingPowerInContract: seasonTotalVotingPowerCast[season],
timestamp: block.timestamp
});

Expand All @@ -155,13 +155,13 @@ contract SimpleGaugeVoter is
// clear any existing votes
if (isVoting(_tokenId)) _reset(_tokenId);

uint16 season = IClockSeason(clock).currentSeason();
uint16 season = IClockSeason(clock).currentSeasonIndex();

// voting power continues to increase over the voting epoch.
// this means you can revote later in the epoch to increase votes.
// while not a huge problem, it's worth noting that when rewards are fully
// on chain, this could be a vector for gaming.
TokenVoteData storage voteData = tokenVoteData[season][_tokenId];
TokenVoteData storage voteData = seasonTokenVoteData[season][_tokenId];
uint256 sumOfWeights = 0;

for (uint256 i = 0; i < numVotes; i++) {
Expand Down Expand Up @@ -191,8 +191,8 @@ contract SimpleGaugeVoter is

function _reset(uint256 _tokenId) internal {
// get what we need
uint16 season = IClockSeason(clock).currentSeason();
TokenVoteData storage voteData = tokenVoteData[season][_tokenId];
uint16 season = IClockSeason(clock).currentSeasonIndex();
TokenVoteData storage voteData = seasonTokenVoteData[season][_tokenId];
address[] storage pastVotes = voteData.gaugesVotedFor;

// reset the global state variables we don't need
Expand All @@ -205,8 +205,8 @@ contract SimpleGaugeVoter is
uint256 _votes = voteData.votes[gauge];

// remove from the total globals
gaugeVotes[season][gauge] -= _votes;
totalVotingPowerCast[season] -= _votes;
seasonGaugeVotes[season][gauge] -= _votes;
seasonTotalVotingPowerCast[season] -= _votes;

delete voteData.votes[gauge];

Expand All @@ -216,8 +216,8 @@ contract SimpleGaugeVoter is
epoch: epochId(),
tokenId: _tokenId,
votingPowerRemovedFromGauge: _votes,
totalVotingPowerInGauge: gaugeVotes[season][gauge],
totalVotingPowerInContract: totalVotingPowerCast[season],
totalVotingPowerInGauge: seasonGaugeVotes[season][gauge],
totalVotingPowerInContract: seasonTotalVotingPowerCast[season],
timestamp: block.timestamp
});
}
Expand Down Expand Up @@ -319,23 +319,33 @@ contract SimpleGaugeVoter is
}

function isVoting(uint256 _tokenId) public view returns (bool) {
uint16 season = IClockSeason(clock).currentSeason();
return tokenVoteData[season][_tokenId].lastVoted > 0;
uint16 season = IClockSeason(clock).currentSeasonIndex();
return seasonTokenVoteData[season][_tokenId].lastVoted > 0;
}

function votes(uint256 _tokenId, address _gauge) external view returns (uint256) {
uint16 season = IClockSeason(clock).currentSeason();
return tokenVoteData[season][_tokenId].votes[_gauge];
uint16 season = IClockSeason(clock).currentSeasonIndex();
return seasonTokenVoteData[season][_tokenId].votes[_gauge];
}

function gaugesVotedFor(uint256 _tokenId) external view returns (address[] memory) {
uint16 season = IClockSeason(clock).currentSeason();
return tokenVoteData[season][_tokenId].gaugesVotedFor;
uint16 season = IClockSeason(clock).currentSeasonIndex();
return seasonTokenVoteData[season][_tokenId].gaugesVotedFor;
}

function usedVotingPower(uint256 _tokenId) external view returns (uint256) {
uint16 season = IClockSeason(clock).currentSeason();
return tokenVoteData[season][_tokenId].usedVotingPower;
uint16 season = IClockSeason(clock).currentSeasonIndex();
return seasonTokenVoteData[season][_tokenId].usedVotingPower;
}

function totalVotingPowerCast() public view returns (uint256) {
uint16 season = IClockSeason(clock).currentSeasonIndex();
return seasonTotalVotingPowerCast[season];
}

function gaugeVotes(address _address) public view returns (uint256) {
uint16 season = IClockSeason(clock).currentSeasonIndex();
return seasonGaugeVotes[season][_address];
}

/// Rest of UUPS logic is handled by OSx plugin
Expand Down
26 changes: 15 additions & 11 deletions test/fork/e2eV2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -873,11 +873,11 @@ contract TestE2EV2 is AragonTest, IWithdrawalQueueErrors, IGaugeVote, IEscrowCur

// check the gauge votes
assertEq(
voter.gaugeVotes(0, gauge0),
voter.gaugeVotes(gauge0),
escrow.votingPower(2) + escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2
);
assertEq(
voter.gaugeVotes(0, gauge1),
voter.gaugeVotes(gauge1),
escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2
);
}
Expand Down Expand Up @@ -962,12 +962,12 @@ contract TestE2EV2 is AragonTest, IWithdrawalQueueErrors, IGaugeVote, IEscrowCur

// check the gauge votes
assertEq(
voter.gaugeVotes(0, gauge0),
voter.gaugeVotes(gauge0),
escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2
);

assertEq(
voter.gaugeVotes(0, gauge1),
voter.gaugeVotes(gauge1),
escrow.votingPower(2) + escrow.votingPower(1) / 2 + escrow.votingPower(3) / 2
);
}
Expand Down Expand Up @@ -1274,9 +1274,9 @@ contract TestE2EV2 is AragonTest, IWithdrawalQueueErrors, IGaugeVote, IEscrowCur
// we check the end state of the contracts
{
// no votes
assertEq(voter.totalVotingPowerCast(0), 0, "Voter should have no votes");
assertEq(voter.gaugeVotes(0, gauge0), 0, "Gauge 0 should have no votes");
assertEq(voter.gaugeVotes(0, gauge1), 0, "Gauge 1 should have no votes");
assertEq(voter.totalVotingPowerCast(), 0, "Voter should have no votes");
assertEq(voter.gaugeVotes(gauge0), 0, "Gauge 0 should have no votes");
assertEq(voter.gaugeVotes(gauge1), 0, "Gauge 1 should have no votes");

// no tokens
assertEq(token.balanceOf(address(escrow)), 0, "Escrow should have no tokens");
Expand Down Expand Up @@ -1304,7 +1304,10 @@ contract TestE2EV2 is AragonTest, IWithdrawalQueueErrors, IGaugeVote, IEscrowCur
vm.warp(nextEpoch);
epochStartTime = block.timestamp;

assertEq(0, clock.currentSeason(), "Season should be 0");
assertEq(0, clock.currentSeasonIndex(), "SeasonIndex should be 0");
(uint start, uint end) = clock.currentSeason();
assertEq(0, start, "Season start timestamp should be 0");
assertEq(0, end, "Season end timestamp should be 0");

// first we give alice and carol some tokens of the underlying
{
Expand All @@ -1330,7 +1333,7 @@ contract TestE2EV2 is AragonTest, IWithdrawalQueueErrors, IGaugeVote, IEscrowCur

_startNewSeason();

assertEq(0, clock.currentSeason(), "Season should still be 0");
assertEq(0, clock.currentSeasonIndex(), "SeasonIndex should still be 0");

// check the token points written
TokenPoint memory tp1_1 = curve.tokenPointHistory(1, 1);
Expand Down Expand Up @@ -1398,7 +1401,8 @@ contract TestE2EV2 is AragonTest, IWithdrawalQueueErrors, IGaugeVote, IEscrowCur
// fast forward to the checkpoint interval alice is warm and has voting power, carol is not
goToEpochStartPlus(clock.checkpointInterval());

assertEq(1, clock.currentSeason(), "Season should be 1");
assertEq(1, clock.currentSeasonIndex(), "SeasonIndex should be 1");


assertEq(escrow.votingPower(1), depositAlice0, "Alice should have voting power");
assertTrue(curve.isWarm(1), "Alice should be warm");
Expand Down Expand Up @@ -1447,7 +1451,7 @@ contract TestE2EV2 is AragonTest, IWithdrawalQueueErrors, IGaugeVote, IEscrowCur

goToEpochStartPlus(clock.checkpointInterval() * 3);

assertEq(2, clock.currentSeason(), "Season should be 2");
assertEq(2, clock.currentSeasonIndex(), "SeasonIndex should be 2");

assertEq(escrow.votingPower(2), 0, "Carol should have no voting power");

Expand Down
Loading

0 comments on commit 2598f50

Please sign in to comment.