Skip to content

Commit

Permalink
Merge pull request #19 from liquity/bribe-initiative-tests
Browse files Browse the repository at this point in the history
Bribe initiative tests
  • Loading branch information
GalloDaSballo authored Oct 14, 2024
2 parents 951e3d6 + 0b211f5 commit 0110a21
Show file tree
Hide file tree
Showing 12 changed files with 1,319 additions and 264 deletions.
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
2 changes: 1 addition & 1 deletion src/ForwardBribe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ contract ForwardBribe is BribeInitiative {
if (boldAmount != 0) bold.safeTransfer(receiver, boldAmount);
if (bribeTokenAmount != 0) bribeToken.safeTransfer(receiver, bribeTokenAmount);
}
}
}
6 changes: 3 additions & 3 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -485,16 +485,16 @@ 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 {}
// Replaces try / catch | Enforces sufficient gas is passed
safeCallWithMinGas(initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onAfterAllocateLQTY, (currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY)));
safeCallWithMinGas(initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onAfterAllocateLQTY, (currentEpoch, msg.sender, userState, allocation, initiativeState)));
}

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

0 comments on commit 0110a21

Please sign in to comment.