Skip to content

Commit 0110a21

Browse files
Merge pull request #19 from liquity/bribe-initiative-tests
Bribe initiative tests
2 parents 951e3d6 + 0b211f5 commit 0110a21

12 files changed

+1319
-264
lines changed

src/BribeInitiative.sol

Lines changed: 67 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.24;
33

4+
import {console} from "forge-std/console.sol";
5+
46
import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
57
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
68

@@ -43,13 +45,13 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
4345
}
4446

4547
/// @inheritdoc IBribeInitiative
46-
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88) {
47-
return totalLQTYAllocationByEpoch.getValue(_epoch);
48+
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88, uint32) {
49+
return _loadTotalLQTYAllocation(_epoch);
4850
}
4951

5052
/// @inheritdoc IBribeInitiative
51-
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88) {
52-
return lqtyAllocationByUserAtEpoch[_user].getValue(_epoch);
53+
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88, uint32) {
54+
return _loadLQTYAllocation(_user, _epoch);
5355
}
5456

5557
/// @inheritdoc IBribeInitiative
@@ -96,9 +98,14 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
9698
"BribeInitiative: invalid-prev-total-lqty-allocation-epoch"
9799
);
98100

99-
boldAmount = uint256(bribe.boldAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value);
100-
bribeTokenAmount =
101-
uint256(bribe.bribeTokenAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value);
101+
(uint88 totalLQTY, uint32 totalAverageTimestamp) = _decodeLQTYAllocation(totalLQTYAllocation.value);
102+
uint240 totalVotes = governance.lqtyToVotes(totalLQTY, block.timestamp, totalAverageTimestamp);
103+
if (totalVotes != 0) {
104+
(uint88 lqty, uint32 averageTimestamp) = _decodeLQTYAllocation(lqtyAllocation.value);
105+
uint240 votes = governance.lqtyToVotes(lqty, block.timestamp, averageTimestamp);
106+
boldAmount = uint256(bribe.boldAmount) * uint256(votes) / uint256(totalVotes);
107+
bribeTokenAmount = uint256(bribe.bribeTokenAmount) * uint256(votes) / uint256(totalVotes);
108+
}
102109

103110
claimedBribeAtEpoch[_user][_epoch] = true;
104111

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

132-
function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _value, bool _insert) private {
139+
function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _lqty, uint32 _averageTimestamp, bool _insert)
140+
private
141+
{
142+
uint224 value = (uint224(_lqty) << 32) | _averageTimestamp;
133143
if (_insert) {
134-
totalLQTYAllocationByEpoch.insert(_epoch, _value, 0);
144+
totalLQTYAllocationByEpoch.insert(_epoch, value, 0);
135145
} else {
136-
totalLQTYAllocationByEpoch.items[_epoch].value = _value;
146+
totalLQTYAllocationByEpoch.items[_epoch].value = value;
137147
}
138-
emit ModifyTotalLQTYAllocation(_epoch, _value);
148+
emit ModifyTotalLQTYAllocation(_epoch, _lqty, _averageTimestamp);
139149
}
140150

141-
function _setLQTYAllocationByUserAtEpoch(address _user, uint16 _epoch, uint88 _value, bool _insert) private {
151+
function _setLQTYAllocationByUserAtEpoch(
152+
address _user,
153+
uint16 _epoch,
154+
uint88 _lqty,
155+
uint32 _averageTimestamp,
156+
bool _insert
157+
) private {
158+
uint224 value = (uint224(_lqty) << 32) | _averageTimestamp;
142159
if (_insert) {
143-
lqtyAllocationByUserAtEpoch[_user].insert(_epoch, _value, 0);
160+
lqtyAllocationByUserAtEpoch[_user].insert(_epoch, value, 0);
144161
} else {
145-
lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = _value;
162+
lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = value;
146163
}
147-
emit ModifyLQTYAllocation(_user, _epoch, _value);
164+
emit ModifyLQTYAllocation(_user, _epoch, _lqty, _averageTimestamp);
148165
}
149166

150-
/// @inheritdoc IInitiative
151-
function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY)
152-
external
153-
virtual
154-
onlyGovernance
155-
{
156-
uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
167+
function _decodeLQTYAllocation(uint224 _value) private pure returns (uint88, uint32) {
168+
return (uint88(_value >> 32), uint32(_value));
169+
}
170+
171+
function _loadTotalLQTYAllocation(uint16 _epoch) private view returns (uint88, uint32) {
172+
return _decodeLQTYAllocation(totalLQTYAllocationByEpoch.items[_epoch].value);
173+
}
174+
175+
function _loadLQTYAllocation(address _user, uint16 _epoch) private view returns (uint88, uint32) {
176+
return _decodeLQTYAllocation(lqtyAllocationByUserAtEpoch[_user].items[_epoch].value);
177+
}
157178

179+
function onAfterAllocateLQTY(
180+
uint16 _currentEpoch,
181+
address _user,
182+
IGovernance.UserState calldata _userState,
183+
IGovernance.Allocation calldata _allocation,
184+
IGovernance.InitiativeState calldata _initiativeState
185+
) external virtual onlyGovernance {
158186
if (_currentEpoch == 0) return;
159187

160-
// if this is the first user allocation in the epoch, then insert a new item into the user allocation DLL
161-
if (mostRecentUserEpoch != _currentEpoch) {
162-
uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].items[mostRecentUserEpoch].value;
163-
uint88 newVoteLQTY = (_vetoLQTY == 0) ? _voteLQTY : 0;
164-
uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();
165-
// if this is the first allocation in the epoch, then insert a new item into the total allocation DLL
166-
if (mostRecentTotalEpoch != _currentEpoch) {
167-
uint88 prevTotalLQTYAllocation = totalLQTYAllocationByEpoch.items[mostRecentTotalEpoch].value;
168-
if (_vetoLQTY == 0) {
169-
// no veto to no veto
170-
_setTotalLQTYAllocationByEpoch(
171-
_currentEpoch, prevTotalLQTYAllocation + newVoteLQTY - prevVoteLQTY, true
172-
);
173-
} else {
174-
if (prevVoteLQTY != 0) {
175-
// if the prev user allocation was counted in, then remove the prev user allocation from the
176-
// total allocation (no veto to veto)
177-
_setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation - prevVoteLQTY, true);
178-
} else {
179-
// veto to veto
180-
_setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation, true);
181-
}
182-
}
183-
} else {
184-
if (_vetoLQTY == 0) {
185-
// no veto to no veto
186-
_setTotalLQTYAllocationByEpoch(
187-
_currentEpoch,
188-
totalLQTYAllocationByEpoch.items[_currentEpoch].value + newVoteLQTY - prevVoteLQTY,
189-
false
190-
);
191-
} else if (prevVoteLQTY != 0) {
192-
// no veto to veto
193-
_setTotalLQTYAllocationByEpoch(
194-
_currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false
195-
);
196-
}
197-
}
198-
// insert a new item into the user allocation DLL
199-
_setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, newVoteLQTY, true);
200-
} else {
201-
uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].getItem(_currentEpoch).value;
202-
if (_vetoLQTY == 0) {
203-
// update the allocation for the current epoch by adding the new allocation and subtracting
204-
// the previous one (no veto to no veto)
205-
_setTotalLQTYAllocationByEpoch(
206-
_currentEpoch,
207-
totalLQTYAllocationByEpoch.items[_currentEpoch].value + _voteLQTY - prevVoteLQTY,
208-
false
209-
);
210-
_setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, _voteLQTY, false);
211-
} else {
212-
// if the user vetoed the initiative, subtract the allocation from the DLLs (no veto to veto)
213-
_setTotalLQTYAllocationByEpoch(
214-
_currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false
215-
);
216-
_setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, 0, false);
217-
}
218-
}
188+
uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
189+
uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();
190+
191+
_setTotalLQTYAllocationByEpoch(
192+
_currentEpoch,
193+
_initiativeState.voteLQTY,
194+
_initiativeState.averageStakingTimestampVoteLQTY,
195+
mostRecentTotalEpoch != _currentEpoch // Insert if current > recent
196+
);
197+
198+
_setLQTYAllocationByUserAtEpoch(
199+
_user,
200+
_currentEpoch,
201+
_allocation.voteLQTY,
202+
_userState.averageStakingTimestamp,
203+
mostRecentUserEpoch != _currentEpoch // Insert if user current > recent
204+
);
219205
}
220206

221207
/// @inheritdoc IInitiative

src/ForwardBribe.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ contract ForwardBribe is BribeInitiative {
2626
if (boldAmount != 0) bold.safeTransfer(receiver, boldAmount);
2727
if (bribeTokenAmount != 0) bribeToken.safeTransfer(receiver, bribeTokenAmount);
2828
}
29-
}
29+
}

src/Governance.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,16 +485,16 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance
485485
emit AllocateLQTY(msg.sender, initiative, deltaLQTYVotes, deltaLQTYVetos, currentEpoch);
486486

487487
// try IInitiative(initiative).onAfterAllocateLQTY(
488-
// currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY
488+
// currentEpoch, msg.sender, userState, allocation, initiativeState
489489
// ) {} catch {}
490490
// Replaces try / catch | Enforces sufficient gas is passed
491-
safeCallWithMinGas(initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onAfterAllocateLQTY, (currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY)));
491+
safeCallWithMinGas(initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onAfterAllocateLQTY, (currentEpoch, msg.sender, userState, allocation, initiativeState)));
492492
}
493493

494494
require(
495495
userState.allocatedLQTY == 0
496496
|| userState.allocatedLQTY <= uint88(stakingV1.stakes(deriveUserProxyAddress(msg.sender))),
497-
"Governance: insufficient-or-unallocated-lqty"
497+
"Governance: insufficient-or-allocated-lqty"
498498
);
499499

500500
globalState = state;

src/interfaces/IBribeInitiative.sol

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {IGovernance} from "./IGovernance.sol";
77

88
interface IBribeInitiative {
99
event DepositBribe(address depositor, uint128 boldAmount, uint128 bribeTokenAmount, uint16 epoch);
10-
event ModifyLQTYAllocation(address user, uint16 epoch, uint88 lqtyAllocated);
11-
event ModifyTotalLQTYAllocation(uint16 epoch, uint88 totalLQTYAllocated);
10+
event ModifyLQTYAllocation(address user, uint16 epoch, uint88 lqtyAllocated, uint32 averageTimestamp);
11+
event ModifyTotalLQTYAllocation(uint16 epoch, uint88 totalLQTYAllocated, uint32 averageTimestamp);
1212
event ClaimBribe(address user, uint16 epoch, uint256 boldAmount, uint256 bribeTokenAmount);
1313

1414
/// @notice Address of the governance contract
@@ -40,12 +40,18 @@ interface IBribeInitiative {
4040
/// @notice Total LQTY allocated to the initiative at a given epoch
4141
/// @param _epoch Epoch at which the LQTY was allocated
4242
/// @return totalLQTYAllocated Total LQTY allocated
43-
function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88 totalLQTYAllocated);
43+
function totalLQTYAllocatedByEpoch(uint16 _epoch)
44+
external
45+
view
46+
returns (uint88 totalLQTYAllocated, uint32 averageTimestamp);
4447
/// @notice LQTY allocated by a user to the initiative at a given epoch
4548
/// @param _user Address of the user
4649
/// @param _epoch Epoch at which the LQTY was allocated by the user
4750
/// @return lqtyAllocated LQTY allocated by the user
48-
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88 lqtyAllocated);
51+
function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch)
52+
external
53+
view
54+
returns (uint88 lqtyAllocated, uint32 averageTimestamp);
4955

5056
/// @notice Deposit bribe tokens for a given epoch
5157
/// @dev The caller has to approve this contract to spend the BOLD and bribe tokens.

src/interfaces/IInitiative.sol

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.24;
33

4+
import {IGovernance} from "./IGovernance.sol";
5+
46
interface IInitiative {
57
/// @notice Callback hook that is called by Governance after the initiative was successfully registered
68
/// @param _atEpoch Epoch at which the initiative is registered
@@ -13,9 +15,16 @@ interface IInitiative {
1315
/// @notice Callback hook that is called by Governance after the LQTY allocation is updated by a user
1416
/// @param _currentEpoch Epoch at which the LQTY allocation is updated
1517
/// @param _user Address of the user that updated their LQTY allocation
16-
/// @param _voteLQTY Allocated voting LQTY
17-
/// @param _vetoLQTY Allocated vetoing LQTY
18-
function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY) external;
18+
/// @param _userState User state
19+
/// @param _allocation Allocation state from user to initiative
20+
/// @param _initiativeState Initiative state
21+
function onAfterAllocateLQTY(
22+
uint16 _currentEpoch,
23+
address _user,
24+
IGovernance.UserState calldata _userState,
25+
IGovernance.Allocation calldata _allocation,
26+
IGovernance.InitiativeState calldata _initiativeState
27+
) external;
1928

2029
/// @notice Callback hook that is called by Governance after the claim for the last epoch was distributed
2130
/// to the initiative

src/utils/DoubleLinkedList.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pragma solidity ^0.8.24;
66
/// and the tail is defined as the null item's next pointer ([tail][prev][item][next][head])
77
library DoubleLinkedList {
88
struct Item {
9-
uint88 value;
9+
uint224 value;
1010
uint16 prev;
1111
uint16 next;
1212
}
@@ -53,7 +53,7 @@ library DoubleLinkedList {
5353
/// @param list Linked list which contains the item
5454
/// @param id Id of the item
5555
/// @return _ Value of the item
56-
function getValue(List storage list, uint16 id) internal view returns (uint88) {
56+
function getValue(List storage list, uint16 id) internal view returns (uint224) {
5757
return list.items[id].value;
5858
}
5959

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

0 commit comments

Comments
 (0)