Skip to content

Commit

Permalink
Merge branch 'dev' into dev-governance-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
GalloDaSballo committed Oct 14, 2024
2 parents a4af757 + 0110a21 commit 78a4e55
Show file tree
Hide file tree
Showing 18 changed files with 1,788 additions and 283 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
28 changes: 22 additions & 6 deletions src/CurveV2GaugeRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,30 @@ contract CurveV2GaugeRewards is BribeInitiative {
duration = _duration;
}

function depositIntoGauge() external returns (uint256) {
uint256 amount = governance.claimForInitiative(address(this));
uint256 public remainder;

bold.approve(address(gauge), amount);
gauge.deposit_reward_token(address(bold), amount, duration);

emit DepositIntoGauge(amount);
/// @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 {
_depositIntoGauge(_bold);
}

function _depositIntoGauge(uint256 amount) internal {

// For small donations queue them into the contract
if(amount < duration * 1000) {
remainder += amount;
return;
}

uint256 total = amount + remainder;
remainder = 0;

return amount;
bold.approve(address(gauge), total);
gauge.deposit_reward_token(address(bold), total, duration);

emit DepositIntoGauge(total);
}

}
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.transfer(receiver, boldAmount);
if (bribeTokenAmount != 0) bribeToken.transfer(receiver, bribeTokenAmount);
}
}
}
25 changes: 18 additions & 7 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ import {UserProxyFactory} from "./UserProxyFactory.sol";
import {add, max, abs} from "./utils/Math.sol";
import {Multicall} from "./utils/Multicall.sol";
import {WAD, PermitParams} from "./utils/Types.sol";
import {safeCallWithMinGas} from "./utils/SafeCallMinGas.sol";

import {SafeCastLib} from "lib/solmate/src/utils/SafeCastLib.sol";

/// @title Governance: Modular Initiative based Governance
contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance {
using SafeERC20 for IERC20;

uint256 constant MIN_GAS_TO_HOOK = 350_000; /// Replace this to ensure hooks have sufficient gas

/// @inheritdoc IGovernance
ILQTYStaking public immutable stakingV1;
/// @inheritdoc IGovernance
Expand Down Expand Up @@ -415,7 +418,9 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance

emit RegisterInitiative(_initiative, msg.sender, currentEpoch);

try IInitiative(_initiative).onRegisterInitiative(currentEpoch) {} catch {}
// try IInitiative(_initiative).onRegisterInitiative(currentEpoch) {} catch {}
// Replaces try / catch | Enforces sufficient gas is passed
safeCallWithMinGas(_initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onRegisterInitiative, (currentEpoch)));
}

/// @inheritdoc IGovernance
Expand Down Expand Up @@ -531,15 +536,17 @@ 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
) {} catch {}
// try IInitiative(initiative).onAfterAllocateLQTY(
// 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, 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 Expand Up @@ -584,7 +591,9 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance

emit UnregisterInitiative(_initiative, currentEpoch);

try IInitiative(_initiative).onUnregisterInitiative(currentEpoch) {} catch {}
// try IInitiative(_initiative).onUnregisterInitiative(currentEpoch) {} catch {}
// Replaces try / catch | Enforces sufficient gas is passed
safeCallWithMinGas(_initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onUnregisterInitiative, (currentEpoch)));
}

/// @inheritdoc IGovernance
Expand Down Expand Up @@ -615,7 +624,9 @@ contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance

emit ClaimForInitiative(_initiative, claimableAmount, votesSnapshot_.forEpoch);

try IInitiative(_initiative).onClaimForInitiative(votesSnapshot_.forEpoch, claimableAmount) {} catch {}
// try IInitiative(_initiative).onClaimForInitiative(votesSnapshot_.forEpoch, claimableAmount) {} catch {}
// Replaces try / catch | Enforces sufficient gas is passed
safeCallWithMinGas(_initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onClaimForInitiative, (votesSnapshot_.forEpoch, claimableAmount)));

return claimableAmount;
}
Expand Down
2 changes: 2 additions & 0 deletions src/UniV4Donations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ contract UniV4Donations is BribeInitiative, BaseHook {
}

function _donateToPool() internal returns (uint256) {
/// @audit TODO: Need to use storage value here I think
/// TODO: Test and fix release speed, which looks off
Vesting memory _vesting = _restartVesting(uint240(governance.claimForInitiative(address(this))));
uint256 amount =
(_vesting.amount * (block.timestamp - vestingEpochStart()) / VESTING_EPOCH_DURATION) - _vesting.released;
Expand Down
4 changes: 2 additions & 2 deletions src/UserProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract UserProxy is IUserProxy {

/// @inheritdoc IUserProxy
function stake(uint256 _amount, address _lqtyFrom) public onlyStakingV2 {
lqty.transferFrom(_lqtyFrom, address(this), _amount);
lqty.safeTransferFrom(_lqtyFrom, address(this), _amount);
lqty.approve(address(stakingV1), _amount);
stakingV1.stake(_amount);
emit Stake(_amount, _lqtyFrom);
Expand Down Expand Up @@ -75,7 +75,7 @@ contract UserProxy is IUserProxy {
ethAmount = address(this).balance;
if (ethAmount > 0) {
(bool success,) = payable(_lusdEthRecipient).call{value: ethAmount}("");
success;
require(success, "UserProxy: eth-fail");
}

emit Unstake(_amount, _lqtyRecipient, _lusdEthRecipient, lusdAmount, ethAmount);
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
Loading

0 comments on commit 78a4e55

Please sign in to comment.