Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bribing accounting by converting lqty to votes #13

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 67 additions & 81 deletions src/BribeInitiative.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {console} from "forge-std/console.sol";

import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

Expand Down Expand Up @@ -43,13 +45,13 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
}

/// @inheritdoc IBribeInitiative
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88) {
return totalLQTYAllocationByEpoch.getValue(_epoch);
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88, uint32) {
return _loadTotalLQTYAllocation(_epoch);
}

/// @inheritdoc IBribeInitiative
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88) {
return lqtyAllocationByUserAtEpoch[_user].getValue(_epoch);
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88, uint32) {
return _loadLQTYAllocation(_user, _epoch);
}

/// @inheritdoc IBribeInitiative
Expand Down Expand Up @@ -96,9 +98,14 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
"BribeInitiative: invalid-prev-total-lqty-allocation-epoch"
);

boldAmount = uint256(bribe.boldAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value);
bribeTokenAmount =
uint256(bribe.bribeTokenAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value);
(uint88 totalLQTY, uint32 totalAverageTimestamp) = _decodeLQTYAllocation(totalLQTYAllocation.value);
uint240 totalVotes = governance.lqtyToVotes(totalLQTY, block.timestamp, totalAverageTimestamp);
if (totalVotes != 0) {
(uint88 lqty, uint32 averageTimestamp) = _decodeLQTYAllocation(lqtyAllocation.value);
uint240 votes = governance.lqtyToVotes(lqty, block.timestamp, averageTimestamp);
boldAmount = uint256(bribe.boldAmount) * uint256(votes) / uint256(totalVotes);
bribeTokenAmount = uint256(bribe.bribeTokenAmount) * uint256(votes) / uint256(totalVotes);
}

claimedBribeAtEpoch[_user][_epoch] = true;

Expand Down Expand Up @@ -129,93 +136,72 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
/// @inheritdoc IInitiative
function onUnregisterInitiative(uint16) external virtual override onlyGovernance {}

function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _value, bool _insert) private {
function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _lqty, uint32 _averageTimestamp, bool _insert)
private
{
uint224 value = (uint224(_lqty) << 32) | _averageTimestamp;
if (_insert) {
totalLQTYAllocationByEpoch.insert(_epoch, _value, 0);
totalLQTYAllocationByEpoch.insert(_epoch, value, 0);
} else {
totalLQTYAllocationByEpoch.items[_epoch].value = _value;
totalLQTYAllocationByEpoch.items[_epoch].value = value;
}
emit ModifyTotalLQTYAllocation(_epoch, _value);
emit ModifyTotalLQTYAllocation(_epoch, _lqty, _averageTimestamp);
}

function _setLQTYAllocationByUserAtEpoch(address _user, uint16 _epoch, uint88 _value, bool _insert) private {
function _setLQTYAllocationByUserAtEpoch(
address _user,
uint16 _epoch,
uint88 _lqty,
uint32 _averageTimestamp,
bool _insert
) private {
uint224 value = (uint224(_lqty) << 32) | _averageTimestamp;
if (_insert) {
lqtyAllocationByUserAtEpoch[_user].insert(_epoch, _value, 0);
lqtyAllocationByUserAtEpoch[_user].insert(_epoch, value, 0);
} else {
lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = _value;
lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = value;
}
emit ModifyLQTYAllocation(_user, _epoch, _value);
emit ModifyLQTYAllocation(_user, _epoch, _lqty, _averageTimestamp);
}

/// @inheritdoc IInitiative
function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY)
external
virtual
onlyGovernance
{
uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
function _decodeLQTYAllocation(uint224 _value) private pure returns (uint88, uint32) {
return (uint88(_value >> 32), uint32(_value));
}

function _loadTotalLQTYAllocation(uint16 _epoch) private view returns (uint88, uint32) {
return _decodeLQTYAllocation(totalLQTYAllocationByEpoch.items[_epoch].value);
}

function _loadLQTYAllocation(address _user, uint16 _epoch) private view returns (uint88, uint32) {
return _decodeLQTYAllocation(lqtyAllocationByUserAtEpoch[_user].items[_epoch].value);
}

function onAfterAllocateLQTY(
uint16 _currentEpoch,
address _user,
IGovernance.UserState calldata _userState,
IGovernance.Allocation calldata _allocation,
IGovernance.InitiativeState calldata _initiativeState
) external virtual onlyGovernance {
if (_currentEpoch == 0) return;

// if this is the first user allocation in the epoch, then insert a new item into the user allocation DLL
if (mostRecentUserEpoch != _currentEpoch) {
uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].items[mostRecentUserEpoch].value;
uint88 newVoteLQTY = (_vetoLQTY == 0) ? _voteLQTY : 0;
uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();
// if this is the first allocation in the epoch, then insert a new item into the total allocation DLL
if (mostRecentTotalEpoch != _currentEpoch) {
uint88 prevTotalLQTYAllocation = totalLQTYAllocationByEpoch.items[mostRecentTotalEpoch].value;
if (_vetoLQTY == 0) {
// no veto to no veto
_setTotalLQTYAllocationByEpoch(
_currentEpoch, prevTotalLQTYAllocation + newVoteLQTY - prevVoteLQTY, true
);
} else {
if (prevVoteLQTY != 0) {
// if the prev user allocation was counted in, then remove the prev user allocation from the
// total allocation (no veto to veto)
_setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation - prevVoteLQTY, true);
} else {
// veto to veto
_setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation, true);
}
}
} else {
if (_vetoLQTY == 0) {
// no veto to no veto
_setTotalLQTYAllocationByEpoch(
_currentEpoch,
totalLQTYAllocationByEpoch.items[_currentEpoch].value + newVoteLQTY - prevVoteLQTY,
false
);
} else if (prevVoteLQTY != 0) {
// no veto to veto
_setTotalLQTYAllocationByEpoch(
_currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false
);
}
}
// insert a new item into the user allocation DLL
_setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, newVoteLQTY, true);
} else {
uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].getItem(_currentEpoch).value;
if (_vetoLQTY == 0) {
// update the allocation for the current epoch by adding the new allocation and subtracting
// the previous one (no veto to no veto)
_setTotalLQTYAllocationByEpoch(
_currentEpoch,
totalLQTYAllocationByEpoch.items[_currentEpoch].value + _voteLQTY - prevVoteLQTY,
false
);
_setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, _voteLQTY, false);
} else {
// if the user vetoed the initiative, subtract the allocation from the DLLs (no veto to veto)
_setTotalLQTYAllocationByEpoch(
_currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false
);
_setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, 0, false);
}
}
uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();

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

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

/// @inheritdoc IInitiative
Expand Down
4 changes: 2 additions & 2 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -478,14 +478,14 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance
emit AllocateLQTY(msg.sender, initiative, deltaLQTYVotes, deltaLQTYVetos, currentEpoch);

try IInitiative(initiative).onAfterAllocateLQTY(
currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY
currentEpoch, msg.sender, userState, allocation, initiativeState
) {} catch {}
}

require(
userState.allocatedLQTY == 0
|| userState.allocatedLQTY <= uint88(stakingV1.stakes(deriveUserProxyAddress(msg.sender))),
"Governance: insufficient-or-unallocated-lqty"
"Governance: insufficient-or-allocated-lqty"
);

globalState = state;
Expand Down
14 changes: 10 additions & 4 deletions src/interfaces/IBribeInitiative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {IGovernance} from "./IGovernance.sol";

interface IBribeInitiative {
event DepositBribe(address depositor, uint128 boldAmount, uint128 bribeTokenAmount, uint16 epoch);
event ModifyLQTYAllocation(address user, uint16 epoch, uint88 lqtyAllocated);
event ModifyTotalLQTYAllocation(uint16 epoch, uint88 totalLQTYAllocated);
event ModifyLQTYAllocation(address user, uint16 epoch, uint88 lqtyAllocated, uint32 averageTimestamp);
event ModifyTotalLQTYAllocation(uint16 epoch, uint88 totalLQTYAllocated, uint32 averageTimestamp);
event ClaimBribe(address user, uint16 epoch, uint256 boldAmount, uint256 bribeTokenAmount);

/// @notice Address of the governance contract
Expand Down Expand Up @@ -40,12 +40,18 @@ interface IBribeInitiative {
/// @notice Total LQTY allocated to the initiative at a given epoch
/// @param _epoch Epoch at which the LQTY was allocated
/// @return totalLQTYAllocated Total LQTY allocated
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88 totalLQTYAllocated);
function totalLQTYAllocatedByEpoch(uint16 _epoch)
external
view
returns (uint88 totalLQTYAllocated, uint32 averageTimestamp);
/// @notice LQTY allocated by a user to the initiative at a given epoch
/// @param _user Address of the user
/// @param _epoch Epoch at which the LQTY was allocated by the user
/// @return lqtyAllocated LQTY allocated by the user
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88 lqtyAllocated);
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch)
external
view
returns (uint88 lqtyAllocated, uint32 averageTimestamp);

/// @notice Deposit bribe tokens for a given epoch
/// @dev The caller has to approve this contract to spend the BOLD and bribe tokens.
Expand Down
15 changes: 12 additions & 3 deletions src/interfaces/IInitiative.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IGovernance} from "./IGovernance.sol";

interface IInitiative {
/// @notice Callback hook that is called by Governance after the initiative was successfully registered
/// @param _atEpoch Epoch at which the initiative is registered
Expand All @@ -13,9 +15,16 @@ interface IInitiative {
/// @notice Callback hook that is called by Governance after the LQTY allocation is updated by a user
/// @param _currentEpoch Epoch at which the LQTY allocation is updated
/// @param _user Address of the user that updated their LQTY allocation
/// @param _voteLQTY Allocated voting LQTY
/// @param _vetoLQTY Allocated vetoing LQTY
function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY) external;
/// @param _userState User state
/// @param _allocation Allocation state from user to initiative
/// @param _initiativeState Initiative state
function onAfterAllocateLQTY(
uint16 _currentEpoch,
address _user,
IGovernance.UserState calldata _userState,
IGovernance.Allocation calldata _allocation,
IGovernance.InitiativeState calldata _initiativeState
) external;

/// @notice Callback hook that is called by Governance after the claim for the last epoch was distributed
/// to the initiative
Expand Down
6 changes: 3 additions & 3 deletions src/utils/DoubleLinkedList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pragma solidity ^0.8.24;
/// and the tail is defined as the null item's next pointer ([tail][prev][item][next][head])
library DoubleLinkedList {
struct Item {
uint88 value;
uint224 value;
uint16 prev;
uint16 next;
}
Expand Down Expand Up @@ -53,7 +53,7 @@ library DoubleLinkedList {
/// @param list Linked list which contains the item
/// @param id Id of the item
/// @return _ Value of the item
function getValue(List storage list, uint16 id) internal view returns (uint88) {
function getValue(List storage list, uint16 id) internal view returns (uint224) {
return list.items[id].value;
}

Expand Down Expand Up @@ -81,7 +81,7 @@ library DoubleLinkedList {
/// @param id Id of the item to insert
/// @param value Value of the item to insert
/// @param next Id of the item which should follow item `id`
function insert(List storage list, uint16 id, uint88 value, uint16 next) internal {
function insert(List storage list, uint16 id, uint224 value, uint16 next) internal {
if (contains(list, id)) revert ItemInList();
if (next != 0 && !contains(list, next)) revert ItemNotInList();
uint16 prev = list.items[next].prev;
Expand Down
Loading
Loading