Skip to content

Commit

Permalink
Merge pull request #97 from liquity/refactor_voting_power
Browse files Browse the repository at this point in the history
Voting power refactor with offsets (y-intercept approach)
  • Loading branch information
bingen authored Dec 14, 2024
2 parents 87d79c2 + f37fda0 commit d11e15a
Show file tree
Hide file tree
Showing 41 changed files with 2,486 additions and 2,834 deletions.
118 changes: 49 additions & 69 deletions src/BribeInitiative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import {IBribeInitiative} from "./interfaces/IBribeInitiative.sol";

import {DoubleLinkedList} from "./utils/DoubleLinkedList.sol";

import {EncodingDecodingLib} from "src/utils/EncodingDecodingLib.sol";

contract BribeInitiative is IInitiative, IBribeInitiative {
using SafeERC20 for IERC20;
using DoubleLinkedList for DoubleLinkedList.List;
Expand All @@ -24,9 +22,9 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
IERC20 public immutable bribeToken;

/// @inheritdoc IBribeInitiative
mapping(uint16 => Bribe) public bribeByEpoch;
mapping(uint256 => Bribe) public bribeByEpoch;
/// @inheritdoc IBribeInitiative
mapping(address => mapping(uint16 => bool)) public claimedBribeAtEpoch;
mapping(address => mapping(uint256 => bool)) public claimedBribeAtEpoch;

/// Double linked list of the total LQTY allocated at a given epoch
DoubleLinkedList.List internal totalLQTYAllocationByEpoch;
Expand All @@ -47,18 +45,18 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
}

/// @inheritdoc IBribeInitiative
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88, uint120) {
function totalLQTYAllocatedByEpoch(uint256 _epoch) external view returns (uint256, uint256) {
return _loadTotalLQTYAllocation(_epoch);
}

/// @inheritdoc IBribeInitiative
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88, uint120) {
function lqtyAllocatedByUserAtEpoch(address _user, uint256 _epoch) external view returns (uint256, uint256) {
return _loadLQTYAllocation(_user, _epoch);
}

/// @inheritdoc IBribeInitiative
function depositBribe(uint128 _boldAmount, uint128 _bribeTokenAmount, uint16 _epoch) external {
uint16 epoch = governance.epoch();
function depositBribe(uint256 _boldAmount, uint256 _bribeTokenAmount, uint256 _epoch) external {
uint256 epoch = governance.epoch();
require(_epoch >= epoch, "BribeInitiative: now-or-future-epochs");

Bribe memory bribe = bribeByEpoch[_epoch];
Expand All @@ -72,13 +70,11 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
bribeToken.safeTransferFrom(msg.sender, address(this), _bribeTokenAmount);
}

uint256 constant TIMESTAMP_PRECISION = 1e26;

function _claimBribe(
address _user,
uint16 _epoch,
uint16 _prevLQTYAllocationEpoch,
uint16 _prevTotalLQTYAllocationEpoch
uint256 _epoch,
uint256 _prevLQTYAllocationEpoch,
uint256 _prevTotalLQTYAllocationEpoch
) internal returns (uint256 boldAmount, uint256 bribeTokenAmount) {
require(_epoch < governance.epoch(), "BribeInitiative: cannot-claim-for-current-epoch");
require(!claimedBribeAtEpoch[_user][_epoch], "BribeInitiative: already-claimed");
Expand All @@ -101,26 +97,17 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
"BribeInitiative: invalid-prev-total-lqty-allocation-epoch"
);

(uint88 totalLQTY, uint120 totalAverageTimestamp) = _decodeLQTYAllocation(totalLQTYAllocation.value);
require(totalLQTY > 0, "BribeInitiative: total-lqty-allocation-zero");

// NOTE: SCALING!!! | The timestamp will work until type(uint32).max | After which the math will eventually overflow
uint120 scaledEpochEnd = (
uint120(governance.EPOCH_START()) + uint120(_epoch) * uint120(governance.EPOCH_DURATION())
) * uint120(TIMESTAMP_PRECISION);
require(totalLQTYAllocation.lqty > 0, "BribeInitiative: total-lqty-allocation-zero");

assert(totalAverageTimestamp <= scaledEpochEnd);
uint256 epochEnd = governance.EPOCH_START() + _epoch * governance.EPOCH_DURATION();

uint120 totalAverageAge = scaledEpochEnd - totalAverageTimestamp;
if (totalLQTY != 0 && totalAverageAge != 0) {
(uint88 lqty, uint120 averageTimestamp) = _decodeLQTYAllocation(lqtyAllocation.value);
require(lqty > 0, "BribeInitiative: lqty-allocation-zero");
uint256 totalVotes = governance.lqtyToVotes(totalLQTYAllocation.lqty, epochEnd, totalLQTYAllocation.offset);
if (totalVotes != 0) {
require(lqtyAllocation.lqty > 0, "BribeInitiative: lqty-allocation-zero");

assert(averageTimestamp <= scaledEpochEnd);

uint120 averageAge = scaledEpochEnd - averageTimestamp;
boldAmount = uint256(bribe.boldAmount) * lqty / totalLQTY * averageAge / totalAverageAge;
bribeTokenAmount = uint256(bribe.bribeTokenAmount) * lqty / totalLQTY * averageAge / totalAverageAge;
uint256 votes = governance.lqtyToVotes(lqtyAllocation.lqty, epochEnd, lqtyAllocation.offset);
boldAmount = bribe.boldAmount * votes / totalVotes;
bribeTokenAmount = bribe.bribeTokenAmount * votes / totalVotes;
}

claimedBribeAtEpoch[_user][_epoch] = true;
Expand All @@ -142,9 +129,8 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
bribeTokenAmount += bribeTokenAmount_;
}

// NOTE: Due to rounding errors in the `averageTimestamp` bribes may slightly overpay compared to what they have allocated
// NOTE: Due to rounding errors, bribes may slightly overpay compared to what they have allocated
// We cap to the available amount for this reason
// The error should be below 10 LQTY per annum, in the worst case
if (boldAmount != 0) {
uint256 max = bold.balanceOf(address(this));
if (boldAmount > max) {
Expand All @@ -163,97 +149,91 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
}

/// @inheritdoc IInitiative
function onRegisterInitiative(uint16) external virtual override onlyGovernance {}
function onRegisterInitiative(uint256) external virtual override onlyGovernance {}

/// @inheritdoc IInitiative
function onUnregisterInitiative(uint16) external virtual override onlyGovernance {}
function onUnregisterInitiative(uint256) external virtual override onlyGovernance {}

function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _lqty, uint120 _averageTimestamp, bool _insert)
private
{
uint224 value = _encodeLQTYAllocation(_lqty, _averageTimestamp);
function _setTotalLQTYAllocationByEpoch(uint256 _epoch, uint256 _lqty, uint256 _offset, bool _insert) private {
if (_insert) {
totalLQTYAllocationByEpoch.insert(_epoch, value, 0);
totalLQTYAllocationByEpoch.insert(_epoch, _lqty, _offset, 0);
} else {
totalLQTYAllocationByEpoch.items[_epoch].value = value;
totalLQTYAllocationByEpoch.items[_epoch].lqty = _lqty;
totalLQTYAllocationByEpoch.items[_epoch].offset = _offset;
}
emit ModifyTotalLQTYAllocation(_epoch, _lqty, _averageTimestamp);
emit ModifyTotalLQTYAllocation(_epoch, _lqty, _offset);
}

function _setLQTYAllocationByUserAtEpoch(
address _user,
uint16 _epoch,
uint88 _lqty,
uint120 _averageTimestamp,
uint256 _epoch,
uint256 _lqty,
uint256 _offset,
bool _insert
) private {
uint224 value = _encodeLQTYAllocation(_lqty, _averageTimestamp);
if (_insert) {
lqtyAllocationByUserAtEpoch[_user].insert(_epoch, value, 0);
lqtyAllocationByUserAtEpoch[_user].insert(_epoch, _lqty, _offset, 0);
} else {
lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = value;
lqtyAllocationByUserAtEpoch[_user].items[_epoch].lqty = _lqty;
lqtyAllocationByUserAtEpoch[_user].items[_epoch].offset = _offset;
}
emit ModifyLQTYAllocation(_user, _epoch, _lqty, _averageTimestamp);
emit ModifyLQTYAllocation(_user, _epoch, _lqty, _offset);
}

function _encodeLQTYAllocation(uint88 _lqty, uint120 _averageTimestamp) private pure returns (uint224) {
return EncodingDecodingLib.encodeLQTYAllocation(_lqty, _averageTimestamp);
}
function _loadTotalLQTYAllocation(uint256 _epoch) private view returns (uint256, uint256) {
require(_epoch <= governance.epoch(), "No future Lookup");
DoubleLinkedList.Item memory totalLqtyAllocation = totalLQTYAllocationByEpoch.items[_epoch];

function _decodeLQTYAllocation(uint224 _value) private pure returns (uint88, uint120) {
return EncodingDecodingLib.decodeLQTYAllocation(_value);
return (totalLqtyAllocation.lqty, totalLqtyAllocation.offset);
}

function _loadTotalLQTYAllocation(uint16 _epoch) private view returns (uint88, uint120) {
function _loadLQTYAllocation(address _user, uint256 _epoch) private view returns (uint256, uint256) {
require(_epoch <= governance.epoch(), "No future Lookup");
return _decodeLQTYAllocation(totalLQTYAllocationByEpoch.items[_epoch].value);
}
DoubleLinkedList.Item memory lqtyAllocation = lqtyAllocationByUserAtEpoch[_user].items[_epoch];

function _loadLQTYAllocation(address _user, uint16 _epoch) private view returns (uint88, uint120) {
require(_epoch <= governance.epoch(), "No future Lookup");
return _decodeLQTYAllocation(lqtyAllocationByUserAtEpoch[_user].items[_epoch].value);
return (lqtyAllocation.lqty, lqtyAllocation.offset);
}

/// @inheritdoc IBribeInitiative
function getMostRecentUserEpoch(address _user) external view returns (uint16) {
uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
function getMostRecentUserEpoch(address _user) external view returns (uint256) {
uint256 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();

return mostRecentUserEpoch;
}

/// @inheritdoc IBribeInitiative
function getMostRecentTotalEpoch() external view returns (uint16) {
uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();
function getMostRecentTotalEpoch() external view returns (uint256) {
uint256 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();

return mostRecentTotalEpoch;
}

function onAfterAllocateLQTY(
uint16 _currentEpoch,
uint256 _currentEpoch,
address _user,
IGovernance.UserState calldata _userState,
IGovernance.Allocation calldata _allocation,
IGovernance.InitiativeState calldata _initiativeState
) external virtual onlyGovernance {
uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();
uint256 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
uint256 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();

_setTotalLQTYAllocationByEpoch(
_currentEpoch,
_initiativeState.voteLQTY,
_initiativeState.averageStakingTimestampVoteLQTY,
_initiativeState.voteOffset,
mostRecentTotalEpoch != _currentEpoch // Insert if current > recent
);

_setLQTYAllocationByUserAtEpoch(
_user,
_currentEpoch,
_allocation.voteLQTY,
_userState.averageStakingTimestamp,
_userState.allocatedOffset,
mostRecentUserEpoch != _currentEpoch // Insert if user current > recent
);
}

/// @inheritdoc IInitiative
function onClaimForInitiative(uint16, uint256) external virtual override onlyGovernance {}
function onClaimForInitiative(uint256, uint256) external virtual override onlyGovernance {}
}
2 changes: 1 addition & 1 deletion src/CurveV2GaugeRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract CurveV2GaugeRewards is BribeInitiative {

/// @notice Governance transfers Bold, and we deposit it into the gauge
/// @dev Doing this allows anyone to trigger the claim
function onClaimForInitiative(uint16, uint256 _bold) external override onlyGovernance {
function onClaimForInitiative(uint256, uint256 _bold) external override onlyGovernance {
_depositIntoGauge(_bold);
}

Expand Down
Loading

0 comments on commit d11e15a

Please sign in to comment.