Contract Name:
FeeRecipientFactory
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IFeeRecipientFactory} from "../interfaces/IFeeRecipientFactory.sol";
import {FeeRecipient} from "./../FeeRecipient.sol";
contract FeeRecipientFactory is IFeeRecipientFactory {
/// @inheritdoc IFeeRecipientFactory
address public lastFeeRecipient;
/// @inheritdoc IFeeRecipientFactory
address public treasury;
address public accessHub;
address public immutable voter;
/// @inheritdoc IFeeRecipientFactory
uint256 public feeToTreasury;
/// @inheritdoc IFeeRecipientFactory
mapping(address pair => address feeRecipient) public feeRecipientForPair;
event SetFeeToTreasury(uint256 indexed feeToTreasury);
modifier onlyGovernance() {
require(msg.sender == accessHub);
_;
}
constructor(address _treasury, address _voter, address _accessHub) {
treasury = _treasury;
voter = _voter;
accessHub = _accessHub;
/// @dev start at 8%
feeToTreasury = 800;
}
/// @inheritdoc IFeeRecipientFactory
function createFeeRecipient(
address pair
) external returns (address _feeRecipient) {
/// @dev ensure caller is the voter
require(msg.sender == voter, NOT_AUTHORIZED());
/// @dev create a new feeRecipient
_feeRecipient = address(
new FeeRecipient(pair, msg.sender, address(this))
);
/// @dev dont need to ensure that a feeRecipient wasn't already made previously
feeRecipientForPair[pair] = _feeRecipient;
lastFeeRecipient = _feeRecipient;
}
/// @inheritdoc IFeeRecipientFactory
function setFeeToTreasury(uint256 _feeToTreasury) external onlyGovernance {
/// @dev ensure fee to treasury isn't too high
require(_feeToTreasury <= 10_000, INVALID_TREASURY_FEE());
feeToTreasury = _feeToTreasury;
emit SetFeeToTreasury(_feeToTreasury);
}
/// @inheritdoc IFeeRecipientFactory
function setTreasury(address _treasury) external onlyGovernance {
treasury = _treasury;
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.26;
interface IFeeRecipientFactory {
error INVALID_TREASURY_FEE();
error NOT_AUTHORIZED();
/// @notice the pair fees for a specific pair
/// @param pair the pair to check
/// @return feeRecipient the feeRecipient contract address for the pair
function feeRecipientForPair(
address pair
) external view returns (address feeRecipient);
/// @notice the last feeRecipient address created
/// @return _feeRecipient the address of the last pair fees contract
function lastFeeRecipient() external view returns (address _feeRecipient);
/// @notice create the pair fees for a pair
/// @param pair the address of the pair
/// @return _feeRecipient the address of the newly created feeRecipient
function createFeeRecipient(
address pair
) external returns (address _feeRecipient);
/// @notice the fee % going to the treasury
/// @return _feeToTreasury the fee %
function feeToTreasury() external view returns (uint256 _feeToTreasury);
/// @notice get the treasury address
/// @return _treasury address of the treasury
function treasury() external view returns (address _treasury);
/// @notice set the fee % to be sent to the treasury
/// @param _feeToTreasury the fee % to be sent to the treasury
function setFeeToTreasury(uint256 _feeToTreasury) external;
/// @notice set a new treasury address
/// @param _treasury the new address
function setTreasury(address _treasury) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IFeeDistributor} from "./interfaces/IFeeDistributor.sol";
import {IFeeRecipient} from "./interfaces/IFeeRecipient.sol";
import {IFeeRecipientFactory} from "./interfaces/IFeeRecipientFactory.sol";
/// @notice Pair Fees contract is used as a 1:1 pair relationship to split out fees, this ensures that the curve does not need to be modified for LP shares
contract FeeRecipient is IFeeRecipient {
/// @notice The pair it is bonded to
address public immutable pair;
/// @notice voter contract which fees are gated to be claimed by
address public immutable voter;
/// @notice feedist contract where fees will be sent to
address public feeDistributor;
/// @notice factory contract for feeRecipient (legacy fees)
address public immutable feeRecipientFactory;
constructor(address _pair, address _voter, address _feeRecipientFactory) {
pair = _pair;
voter = _voter;
feeRecipientFactory = _feeRecipientFactory;
}
/// @notice initialize the FeeRecipient contract and approve the LP tokens to the feeDist, gated to voter
function initialize(address _feeDistributor) external {
require(msg.sender == voter, NOT_AUTHORIZED());
feeDistributor = _feeDistributor;
IERC20(pair).approve(_feeDistributor, type(uint256).max);
}
/// @notice notifies the fees
function notifyFees() external {
/// @dev limit calling notifyFees() to the voter contract
require(msg.sender == voter, NOT_AUTHORIZED());
/// @dev fetch balance of LP in the contract
uint256 amount = IERC20(pair).balanceOf(address(this));
/// @dev terminate early if there's no rewards
if (amount == 0) return;
/// @dev calculate treasury share
uint256 feeToTreasury = IFeeRecipientFactory(feeRecipientFactory)
.feeToTreasury();
/// @dev if any to treasury
if (feeToTreasury > 0) {
/// @dev fetch treasury from factory
address treasury = IFeeRecipientFactory(feeRecipientFactory)
.treasury();
/// @dev mulDiv
uint256 amountToTreasury = (amount * feeToTreasury) / 10_000;
/// @dev decrement amount
amount -= amountToTreasury;
/// @dev naked transfer to treasury, no staking
IERC20(pair).transfer(treasury, amountToTreasury);
}
/// @dev if there's any fees
if (amount > 0) {
IFeeDistributor(feeDistributor).notifyRewardAmount(pair, amount);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
interface IFeeDistributor {
error NOT_AUTHORIZED();
error ZERO_AMOUNT();
error NOT_FINALIZED();
error TOKEN_ERROR(address);
event Deposit(address owner, uint256 amount);
event Withdraw(address owner, uint256 amount);
event NotifyReward(
address indexed from,
address indexed reward,
uint256 amount,
uint256 period
);
event VotesIncentivized(
address indexed from,
address indexed reward,
uint256 amount,
uint256 period
);
event ClaimRewards(
uint256 period,
address owner,
address receiver,
address reward,
uint256 amount
);
event RewardsRemoved(address _reward);
/// @notice the address of the voter contract
function voter() external view returns (address);
/// @notice the address of the voting module
function voteModule() external view returns (address);
/// @notice the address of the feeRecipient contract
function feeRecipient() external view returns (address);
/// @notice the first period (epoch) that this contract was deployed
function firstPeriod() external view returns (uint256);
/// @notice balance of the voting power for a user
/// @param owner the owner
/// @return amount the amount of voting share
function balanceOf(address owner) external view returns (uint256 amount);
/// @notice total cumulative amount of voting power per epoch
/// @param period the period to check
/// @return weight the amount of total voting power
function votes(uint256 period) external view returns (uint256 weight);
/// @notice "internal" function gated to voter to add votes
/// @dev internal notation inherited from original solidly, kept for continuity
function _deposit(uint256 amount, address owner) external;
/// @notice "internal" function gated to voter to remove votes
/// @dev internal notation inherited from original solidly, kept for continuity
function _withdraw(uint256 amount, address owner) external;
/// @notice function to claim rewards on behalf of another
/// @param owner owner's address
/// @param tokens an array of the tokens
function getRewardForOwner(address owner, address[] memory tokens) external;
/// @notice function for sending fees directly to be claimable (in system where fees are distro'd through the week)
/// @dev for lumpsum - this would operate similarly to incentivize
/// @param token the address of the token to send for notifying
/// @param amount the amount of token to send
function notifyRewardAmount(address token, uint256 amount) external;
/// @notice gives an array of reward tokens for the feedist
/// @return _rewards array of rewards
function getRewardTokens()
external
view
returns (address[] memory _rewards);
/// @notice shows the earned incentives in the feedist
/// @param token the token address to check
/// @param owner owner's address
/// @return reward the amount earned/claimable
function earned(
address token,
address owner
) external view returns (uint256 reward);
/// @notice function to submit incentives to voters for the upcoming flip
/// @param token the address of the token to send for incentivization
/// @param amount the amount of token to send
function incentivize(address token, uint256 amount) external;
/// @notice get the rewards for a specific period
/// @param owner owner's address
function getPeriodReward(
uint256 period,
address owner,
address token
) external;
/// @notice get the fees and incentives
function getReward(address owner, address[] memory tokens) external;
/// @notice remove a reward from the set
function removeReward(address _token) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
interface IFeeRecipient {
error STF();
error NOT_AUTHORIZED();
function initialize(address _feeDistributor) external;
function notifyFees() external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}