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: dust left after claiming all bribes #123

Merged
merged 2 commits into from
Jan 2, 2025
Merged
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
78 changes: 32 additions & 46 deletions src/BribeInitiative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import {IInitiative} from "./interfaces/IInitiative.sol";
import {IBribeInitiative} from "./interfaces/IBribeInitiative.sol";

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

contract BribeInitiative is IInitiative, IBribeInitiative {
using SafeERC20 for IERC20;
using DoubleLinkedList for DoubleLinkedList.List;

uint256 internal immutable EPOCH_START;
uint256 internal immutable EPOCH_DURATION;

/// @inheritdoc IBribeInitiative
IGovernance public immutable governance;
/// @inheritdoc IBribeInitiative
Expand All @@ -37,6 +41,9 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
governance = IGovernance(_governance);
bold = IERC20(_bold);
bribeToken = IERC20(_bribeToken);

EPOCH_START = governance.EPOCH_START();
EPOCH_DURATION = governance.EPOCH_DURATION();
}

modifier onlyGovernance() {
Expand All @@ -46,23 +53,24 @@ contract BribeInitiative is IInitiative, IBribeInitiative {

/// @inheritdoc IBribeInitiative
function totalLQTYAllocatedByEpoch(uint256 _epoch) external view returns (uint256, uint256) {
return _loadTotalLQTYAllocation(_epoch);
return (totalLQTYAllocationByEpoch.items[_epoch].lqty, totalLQTYAllocationByEpoch.items[_epoch].offset);
}

/// @inheritdoc IBribeInitiative
function lqtyAllocatedByUserAtEpoch(address _user, uint256 _epoch) external view returns (uint256, uint256) {
return _loadLQTYAllocation(_user, _epoch);
return (
lqtyAllocationByUserAtEpoch[_user].items[_epoch].lqty,
lqtyAllocationByUserAtEpoch[_user].items[_epoch].offset
);
}

/// @inheritdoc IBribeInitiative
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];
bribe.boldAmount += _boldAmount;
bribe.bribeTokenAmount += _bribeTokenAmount;
bribeByEpoch[_epoch] = bribe;
bribeByEpoch[_epoch].remainingBoldAmount += _boldAmount;
bribeByEpoch[_epoch].remainingBribeTokenAmount += _bribeTokenAmount;

emit DepositBribe(msg.sender, _boldAmount, _bribeTokenAmount, _epoch);

Expand All @@ -80,7 +88,7 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
require(!claimedBribeAtEpoch[_user][_epoch], "BribeInitiative: already-claimed");

Bribe memory bribe = bribeByEpoch[_epoch];
require(bribe.boldAmount != 0 || bribe.bribeTokenAmount != 0, "BribeInitiative: no-bribe");
require(bribe.remainingBoldAmount != 0 || bribe.remainingBribeTokenAmount != 0, "BribeInitiative: no-bribe");

DoubleLinkedList.Item memory lqtyAllocation =
lqtyAllocationByUserAtEpoch[_user].getItem(_prevLQTYAllocationEpoch);
Expand All @@ -98,18 +106,25 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
);

require(totalLQTYAllocation.lqty > 0, "BribeInitiative: total-lqty-allocation-zero");
require(lqtyAllocation.lqty > 0, "BribeInitiative: lqty-allocation-zero");

uint256 epochEnd = governance.EPOCH_START() + _epoch * governance.EPOCH_DURATION();
// `Governance` guarantees that `votes` evaluates to 0 or greater for each initiative at the time of allocation.
// Since the last possible moment to allocate within this epoch is 1 second before `epochEnd`, we have that:
// - `lqtyAllocation.lqty > 0` implies `votes > 0`
// - `totalLQTYAllocation.lqty > 0` implies `totalVotes > 0`

uint256 totalVotes = governance.lqtyToVotes(totalLQTYAllocation.lqty, epochEnd, totalLQTYAllocation.offset);
if (totalVotes != 0) {
require(lqtyAllocation.lqty > 0, "BribeInitiative: lqty-allocation-zero");
uint256 epochEnd = EPOCH_START + _epoch * EPOCH_DURATION;
uint256 totalVotes = _lqtyToVotes(totalLQTYAllocation.lqty, epochEnd, totalLQTYAllocation.offset);
uint256 votes = _lqtyToVotes(lqtyAllocation.lqty, epochEnd, lqtyAllocation.offset);
uint256 remainingVotes = totalVotes - bribe.claimedVotes;

uint256 votes = governance.lqtyToVotes(lqtyAllocation.lqty, epochEnd, lqtyAllocation.offset);
boldAmount = bribe.boldAmount * votes / totalVotes;
bribeTokenAmount = bribe.bribeTokenAmount * votes / totalVotes;
}
boldAmount = bribe.remainingBoldAmount * votes / remainingVotes;
bribeTokenAmount = bribe.remainingBribeTokenAmount * votes / remainingVotes;
bribe.remainingBoldAmount -= boldAmount;
bribe.remainingBribeTokenAmount -= bribeTokenAmount;
bribe.claimedVotes += votes;

bribeByEpoch[_epoch] = bribe;
claimedBribeAtEpoch[_user][_epoch] = true;

emit ClaimBribe(_user, _epoch, boldAmount, bribeTokenAmount);
Expand All @@ -129,23 +144,8 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
bribeTokenAmount += bribeTokenAmount_;
}

// NOTE: Due to rounding errors, bribes may slightly overpay compared to what they have allocated
// We cap to the available amount for this reason
if (boldAmount != 0) {
uint256 max = bold.balanceOf(address(this));
if (boldAmount > max) {
boldAmount = max;
}
bold.safeTransfer(msg.sender, boldAmount);
}

if (bribeTokenAmount != 0) {
uint256 max = bribeToken.balanceOf(address(this));
if (bribeTokenAmount > max) {
bribeTokenAmount = max;
}
bribeToken.safeTransfer(msg.sender, bribeTokenAmount);
}
if (boldAmount != 0) bold.safeTransfer(msg.sender, boldAmount);
if (bribeTokenAmount != 0) bribeToken.safeTransfer(msg.sender, bribeTokenAmount);
}

/// @inheritdoc IInitiative
Expand Down Expand Up @@ -180,20 +180,6 @@ contract BribeInitiative is IInitiative, IBribeInitiative {
emit ModifyLQTYAllocation(_user, _epoch, _lqty, _offset);
}

function _loadTotalLQTYAllocation(uint256 _epoch) private view returns (uint256, uint256) {
require(_epoch <= governance.epoch(), "No future Lookup");
DoubleLinkedList.Item memory totalLqtyAllocation = totalLQTYAllocationByEpoch.items[_epoch];

return (totalLqtyAllocation.lqty, totalLqtyAllocation.offset);
}

function _loadLQTYAllocation(address _user, uint256 _epoch) private view returns (uint256, uint256) {
require(_epoch <= governance.epoch(), "No future Lookup");
DoubleLinkedList.Item memory lqtyAllocation = lqtyAllocationByUserAtEpoch[_user].items[_epoch];

return (lqtyAllocation.lqty, lqtyAllocation.offset);
}

/// @inheritdoc IBribeInitiative
function getMostRecentUserEpoch(address _user) external view returns (uint256) {
uint256 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
Expand Down
4 changes: 2 additions & 2 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {MultiDelegateCall} from "./utils/MultiDelegateCall.sol";
import {WAD, PermitParams} from "./utils/Types.sol";
import {safeCallWithMinGas} from "./utils/SafeCallMinGas.sol";
import {Ownable} from "./utils/Ownable.sol";
import {_lqtyToVotes} from "./utils/VotingPower.sol";

/// @title Governance: Modular Initiative based Governance
contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Ownable, IGovernance {
Expand Down Expand Up @@ -266,8 +267,7 @@ contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Own

/// @inheritdoc IGovernance
function lqtyToVotes(uint256 _lqtyAmount, uint256 _timestamp, uint256 _offset) public pure returns (uint256) {
uint256 prod = _lqtyAmount * _timestamp;
return prod > _offset ? prod - _offset : 0;
return _lqtyToVotes(_lqtyAmount, _timestamp, _offset);
}

/*//////////////////////////////////////////////////////////////
Expand Down
15 changes: 10 additions & 5 deletions src/interfaces/IBribeInitiative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,20 @@ interface IBribeInitiative {
function bribeToken() external view returns (IERC20 bribeToken);

struct Bribe {
uint256 boldAmount;
uint256 bribeTokenAmount; // [scaled as 10 ** bribeToken.decimals()]
uint256 remainingBoldAmount;
uint256 remainingBribeTokenAmount; // [scaled as 10 ** bribeToken.decimals()]
uint256 claimedVotes;
}

/// @notice Amount of bribe tokens deposited for a given epoch
/// @param _epoch Epoch at which the bribe was deposited
/// @return boldAmount Amount of BOLD tokens deposited
/// @return bribeTokenAmount Amount of bribe tokens deposited
function bribeByEpoch(uint256 _epoch) external view returns (uint256 boldAmount, uint256 bribeTokenAmount);
/// @return remainingBoldAmount Amount of BOLD tokens that haven't been claimed yet
/// @return remainingBribeTokenAmount Amount of bribe tokens that haven't been claimed yet
/// @return claimedVotes Sum of voting power of users who have already claimed their bribes
function bribeByEpoch(uint256 _epoch)
external
view
returns (uint256 remainingBoldAmount, uint256 remainingBribeTokenAmount, uint256 claimedVotes);
/// @notice Check if a user has claimed bribes for a given epoch
/// @param _user Address of the user
/// @param _epoch Epoch at which the bribe may have been claimed by the user
Expand Down
7 changes: 7 additions & 0 deletions src/utils/VotingPower.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

function _lqtyToVotes(uint256 _lqtyAmount, uint256 _timestamp, uint256 _offset) pure returns (uint256) {
uint256 prod = _lqtyAmount * _timestamp;
return prod > _offset ? prod - _offset : 0;
}
Loading
Loading