// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ERC20, ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ILute} from "./interfaces/ILute.sol";
/**
* @title Lute ERC20 Token, Which is the main protocol token
* @author The Lute Protocol team
* @dev This contract extends the ERC20Burnable, and Ownable contracts from OpenZeppelin,
* providing a comprehensive implementation of a standard ERC20 token with burnable and minting features.
* The Lute token allows for minting of new tokens, which can only be initiated by the {EmmisionManager}
* contract in standard use.
*/
contract Lute is ILute, ERC20Burnable, Ownable {
/**
* @dev Initializes the contract, giving the transferred address the right to mint
* and also mints the initial supply
*
* @param minter_ Address that will be granted ownership and minting rights
*/
constructor(address minter_) ERC20("Alandale", "LUTE") Ownable() {
_mint(msg.sender, 500_000_000e18);
_transferOwnership(minter_);
}
/**
* @dev Allows the contract owner to mint new tokens to a specified address.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
* - Can only be called by the contract owner.
* - `to_` cannot be the zero address.
*
* @param to_ The address to receive the minted tokens.
* @param amount_ The number of tokens to mint.
*/
function mint(address to_, uint256 amount_) external virtual override onlyOwner {
_mint(to_, amount_);
}
}
imports228 files
contracts/mocks/VoterMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract VoterMock {
mapping(address => bool) public isGauge;
mapping(address => address) public poolForGauge;
address public token;
function setGauge(address gauge_, address pool_) external {
isGauge[gauge_] = true;
poolForGauge[gauge_] = pool_;
}
function setToken(address token_) external {
token = token_;
}
function notifyRewardAmount(uint256 amount_) external {
ERC20(token).transferFrom(msg.sender, address(this), amount_);
}
event OnAfterTokenTransfer(address caller_, address from_, address to_, uint256 tokenId_);
event OnAfterTokenMerge(address caller_, uint256 fromTokenId_, uint256 toTokenId_);
function onAfterTokenTransfer(address from_, address to_, uint256 tokenId_) external {
emit OnAfterTokenTransfer(msg.sender, from_, to_, tokenId_);
}
function onAfterTokenMerge(uint256 fromTokenId_, uint256 toTokenId_) external {
emit OnAfterTokenMerge(msg.sender, fromTokenId_, toTokenId_);
}
}
contracts/mocks/SingelTokenBuybackUpgradeableMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {SingelTokenBuybackUpgradeable} from "../lute/SingelTokenBuybackUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract SingelTokenBuybackUpgradeableMock is OwnableUpgradeable, SingelTokenBuybackUpgradeable {
address public token;
function initialize(address pathProivderV2_, address targetToken_) external initializer {
__Ownable_init();
__SingelTokenBuyback__init(pathProivderV2_);
token = targetToken_;
}
function _checkBuybackSwapPermissions() internal view virtual override onlyOwner {}
function _getBuybackTargetToken() internal view virtual override returns (address) {
return token;
}
}
@openzeppelin/contracts/token/ERC20/ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
contracts/core/WHypeAirdrop.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {MerkleProofUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
/**
* @title WHypeAirdrop
* @notice Merkle-proof based airdrop contract for WHYPE token distribution.
* @dev The contract expects the developer/operator to transfer enough WHYPE tokens
* to this contract address before users start claiming.
* @author Aegas
*/
contract WHypeAirdrop is AccessControlUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @notice Emitted when a new Merkle root hash is configured.
/// @param rootHash Newly configured Merkle root.
event RootHashUpdated(bytes32 rootHash);
/// @notice Emitted when a user claims tokens.
/// @param user Recipient account.
/// @param rootHashAmount Total cumulative amount allocated to the user in the active root.
/// @param claimedAmount Amount transferred in the current claim call.
event Claimed(address indexed user, uint256 rootHashAmount, uint256 claimedAmount);
/// @notice Emitted when admin recovers ERC20 tokens while paused.
/// @param token Token address that was recovered.
/// @param recipient Receiver of the recovered tokens.
/// @param amount Amount of recovered tokens.
event EmergencyRecovered(address indexed token, address indexed recipient, uint256 amount);
/// @notice Thrown when one of the provided addresses is zero.
error ZeroAddress();
/// @notice Thrown when provided amount is zero.
error ZeroAmount();
/// @notice Thrown when a zero Merkle root is provided.
error ZeroRootHash();
/// @notice Thrown when Merkle proof validation fails.
error InvalidMerkleProof();
/// @notice Thrown when user already claimed full allocated amount.
error AlreadyClaimed();
/// @notice Role that can update Merkle root.
bytes32 public constant ROOT_SETTER_ROLE = keccak256("ROOT_SETTER_ROLE");
/// @notice WHYPE ERC20 token address used for airdrop payouts.
address public whype;
/// @notice Active Merkle root with cumulative user allocations.
bytes32 public rootHash;
/// @notice Tracks cumulative amount already claimed by each user.
mapping(address => uint256) public claimed;
/// @dev Disables initializers on implementation contract.
constructor() {
_disableInitializers();
}
/// @notice Initializes access control, reentrancy guard and pausable state.
/// @dev Grants admin and root-setter roles to initializer caller.
function initialize(address whype_) external initializer {
if (whype_ == address(0)) revert ZeroAddress();
__AccessControl_init();
__ReentrancyGuard_init();
__Pausable_init();
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_grantRole(ROOT_SETTER_ROLE, _msgSender());
whype = whype_;
}
// ADMIN FUNCTIONS
/// @notice Updates active Merkle root.
/// @param rootHash_ New Merkle root hash.
function setRootHash(bytes32 rootHash_) external onlyRole(ROOT_SETTER_ROLE) whenNotPaused {
if (rootHash_ == bytes32(0)) revert ZeroRootHash();
rootHash = rootHash_;
emit RootHashUpdated(rootHash_);
}
/// @notice Pauses claiming and root updates.
function pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
}
/// @notice Unpauses claiming and root updates.
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
/// @notice Emergency function to recover ERC20 tokens while paused.
/// @param token_ Token to recover.
/// @param recipient_ Recipient of recovered tokens.
/// @param amount_ Amount to recover.
function emergencyRecoverERC20(address token_, address recipient_, uint256 amount_) external onlyRole(DEFAULT_ADMIN_ROLE) whenPaused {
if (token_ == address(0) || recipient_ == address(0)) revert ZeroAddress();
if (amount_ == 0) revert ZeroAmount();
IERC20Upgradeable(token_).safeTransfer(recipient_, amount_);
emit EmergencyRecovered(token_, recipient_, amount_);
}
// USER FUNCTIONS
/// @notice Claims WHYPE using a Merkle proof and cumulative allocation.
/// @dev `amount_` is cumulative entitlement from the root. The function transfers
/// only the delta between `amount_` and already claimed amount.
/// @param proof Merkle proof for (`addr_`, `amount_`) leaf.
/// @param addr_ Address to receive WHYPE.
/// @param amount_ Cumulative allocation for `addr_` in active root.
function claim(bytes32[] memory proof, address addr_, uint256 amount_) external nonReentrant whenNotPaused {
if (!MerkleProofUpgradeable.verify(proof, rootHash, keccak256(bytes.concat(keccak256(abi.encode(addr_, amount_))))))
revert InvalidMerkleProof();
uint256 claimedAmountCache = claimed[addr_];
if (claimedAmountCache >= amount_) revert AlreadyClaimed();
uint256 toTransferAmount = amount_ - claimedAmountCache;
claimed[addr_] = amount_;
IERC20Upgradeable(whype).safeTransfer(addr_, toTransferAmount);
emit Claimed(addr_, amount_, toTransferAmount);
}
}
@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControlUpgradeable {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
@openzeppelin/contracts/proxy/beacon/IBeacon.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
contracts/core/interfaces/ITokenPublicRaise.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
/**
* @title ITokenPublicRaise
* @notice Interface for a fixed-rate public raise that accepts native currency and
* accounts purchased token amounts per depositor.
*/
interface ITokenPublicRaise {
/**
* @notice Emitted on a successful deposit during the active raise window.
* @param user Depositor address.
* @param amountIn Accepted native amount in wei (may be capped by per-user/global limits).
* @param tokensOut Accounted amount of sale tokens purchased for `amountIn`
* using the current fixed exchange rate.
*/
event Deposited(address indexed user, uint256 amountIn, uint256 tokensOut);
/**
* @notice Emitted when the treasury address is updated by the owner.
* @param newTreasury The new destination address for collected native funds.
*/
event TreasuryUpdated(address indexed newTreasury);
/**
* @notice Emitted when deposit limits are updated by the owner.
* @param minDepositAmount Suggested minimum per-transaction deposit (native units, wei).
* @param maxDepositAmount Maximum total native amount a single user may deposit (wei).
* @param totalDepositCap Global cap for total native deposits across all users (wei).
*/
event DepositLimitsUpdated(uint256 minDepositAmount, uint256 maxDepositAmount, uint256 totalDepositCap);
/**
* @notice Emitted when the raise window (start/end timestamps) is updated by the owner.
* @param startTimestamp Inclusive start timestamp.
* @param endTimestamp Inclusive end timestamp.
*/
event RaiseWindowUpdated(uint256 startTimestamp, uint256 endTimestamp);
/**
* @notice Emitted when the fixed exchange rate is updated by the owner.
* @dev The rate is expressed as "tokens per 1e18 native units".
* @param tokenPricePerOneNative Number of tokens allocated per 1e18 native units (wei).
*/
event ExchangeRateUpdated(uint256 tokenPricePerOneNative);
/**
* @notice Emitted when native funds are withdrawn to the treasury after the raise ends.
* @param treasury Treasury address that received the funds.
* @param amount Amount of native currency (wei) transferred to `treasury`.
*/
event TreasuryWithdrawn(address indexed treasury, uint256 amount);
/**
* @notice Initializes the raise configuration (proxy initializer).
* @param startTimestamp_ Inclusive sale start timestamp.
* @param endTimestamp_ Inclusive sale end timestamp.
* @param minDepositAmount_ Suggested minimum per-transaction deposit (wei).
* @param maxDepositAmount_ Maximum total native amount per user (wei).
* @param totalDepositCap_ Global cap across all users (wei).
* @param tokenPricePerOneNative_ Tokens per 1e18 native units (wei-denominated rate).
* @param treasury_ Destination address for collected native funds.
*/
function initialize(
uint256 startTimestamp_,
uint256 endTimestamp_,
uint256 minDepositAmount_,
uint256 maxDepositAmount_,
uint256 totalDepositCap_,
uint256 tokenPricePerOneNative_,
address treasury_
) external;
/**
* @notice Deposits native currency during the active raise window at the fixed rate.
* @dev The effective accepted amount may be capped by per-user and/or global remaining allowances.
*/
function deposit() external payable;
/**
* @notice Withdraws the entire native balance to the treasury after the raise has ended.
* @dev Only callable by the owner in the implementation.
*/
function withdrawToTreasury() external;
/**
* @notice Updates the fixed exchange rate (tokens per 1e18 native units).
* @param tokenPricePerOneNative_ New price (tokens per 1e18 native units).
*/
function setTokenPricePerOneNative(uint256 tokenPricePerOneNative_) external;
/**
* @notice Updates the treasury address.
* @param treasury_ New treasury address.
*/
function setTreasury(address treasury_) external;
/**
* @notice Updates min/per-user/global deposit limits.
* @param minDepositAmount_ Suggested minimum per-transaction deposit (wei).
* @param maxDepositAmount_ Maximum total native amount per user (wei).
* @param totalDepositCap_ Global cap across all users (wei).
*/
function setDepositLimits(
uint256 minDepositAmount_,
uint256 maxDepositAmount_,
uint256 totalDepositCap_
) external;
/**
* @notice Updates the start/end timestamps of the raise window.
* @param startTimestamp_ Inclusive start timestamp.
* @param endTimestamp_ Inclusive end timestamp.
*/
function setRaiseWindow(uint256 startTimestamp_, uint256 endTimestamp_) external;
/**
* @notice Returns the maximum additional native amount `user_` can still deposit (in wei).
* @param user_ The user address to query.
* @return maxAllowed Maximum additional deposit permitted for `user_`.
*/
function maxDeposit(address user_) external view returns (uint256 maxAllowed);
/**
* @notice Returns whether the raise window is currently active.
* @return active True if `block.timestamp` ∈ [startTimestamp, endTimestamp], false otherwise.
*/
function isRaiseActive() external view returns (bool active);
/**
* @notice Returns a snapshot of global config/state and the user’s counters.
* @param user_ Address to query (use zero address for global-only fields).
* @return active Whether the raise is active.
* @return start Start timestamp.
* @return end End timestamp.
* @return min Suggested minimum per-transaction deposit.
* @return max Maximum per-user deposit.
* @return globalCap Global deposit cap.
* @return price Tokens per 1e18 native units.
* @return totalIn Total native deposited.
* @return userIn User’s native deposited (0 for zero address).
* @return userOut User’s accounted purchased tokens (0 for zero address).
* @return userMaxDeposit User’s remaining allowed deposit (0 for zero address).
*/
function getInfo(address user_)
external
view
returns (
bool active,
uint256 start,
uint256 end,
uint256 min,
uint256 max,
uint256 globalCap,
uint256 price,
uint256 totalIn,
uint256 userIn,
uint256 userOut,
uint256 userMaxDeposit
);
}
@openzeppelin/contracts/access/Ownable2Step.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
contracts/fees/interfaces/IFeesVaultFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IAccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol";
import {IAlgebraVaultFactory} from "@cryptoalgebra/integral-core/contracts/interfaces/vault/IAlgebraVaultFactory.sol";
import {IFeesVault} from "./IFeesVault.sol";
/**
* @title IFeesVaultFactory Interface
* @dev Interface for the `FeesVaultFactory` contract. It defines the events, errors,
* and functions related to the creation and management of fee vaults for pools.
*
* This interface extends `IAlgebraVaultFactory`, inheriting its functionalities
* and integrating them with specific requirements for fee vault management.
*/
interface IFeesVaultFactory is IAlgebraVaultFactory, IAccessControlUpgradeable {
/**
* @dev Structure for holding distribution configuration details.
* Used to set how fees are distributed to various recipients including gauges.
*/
struct DistributionConfig {
uint256 toGaugeRate; // The rate at which fees are distributed to the gauge.
address[] recipients; // The addresses of the recipients who will receive the fees.
uint256[] rates; // The rates at which fees are distributed to each recipient.
}
/**
* @dev Emitted when a default distribution configuration is change.
* @param config The distribution configuration applied to all fees vault.
*/
event DefaultDistributionConfig(DistributionConfig config);
/**
* @dev Emitted when a custom distribution configuration is set for a fees vault.
* @param feesVault The address of the fees vault for which the configuration is set.
* @param config The custom distribution configuration applied to the fees vault.
*/
event CustomDistributionConfig(address indexed feesVault, DistributionConfig config);
/**
* @dev Emitted when the implementation of the fees vault is changed.
* This allows the system to upgrade the fees vault logic.
* @param oldImplementation The address of the previous fees vault implementation.
* @param newImplementation The address of the new fees vault implementation.
*/
event FeesVaultImplementationChanged(address indexed oldImplementation, address indexed newImplementation);
/**
* @dev Emitted when a new FeesVault is created for a pool.
*
* @param pool Address of the pool for which the FeesVault was created.
* @param feesVault Address of the newly created FeesVault.
*/
event FeesVaultCreated(address indexed pool, address indexed feesVault);
/**
* @dev Emitted when the voter address is updated. This address is used for voting in fee vaults.
*
* @param oldVoter The address of the previous voter.
* @param newVoter The address of the new voter that has been set.
*/
event SetVoter(address indexed oldVoter, address indexed newVoter);
/**
* @dev Emitted when a custom distribution configuration is set for a creator.
* @param creator The address of the creator for which the configuration is set.
* @param config The custom distribution configuration applied to the fees vault created by creator.
*/
event CreatorDistributionConfig(address indexed creator, DistributionConfig config);
/**
* @dev Emitted when the creator for multiple fees vaults is changed.
* @param creator_ The new creator address associated with the fees vaults.
* @param feesVaults The array of fees vault addresses that had their creator changed.
*/
event ChangeCreatorForFeesVaults(address indexed creator_, address[] feesVaults);
/**
* @dev Error indicating that a fee vault creation attempt was made for a pool that already has an associated vault.
*/
error AlreadyCreated();
/**
* @dev Error indicating that an action (such as creating a fee vault) was attempted by an address that is not whitelisted.
*/
error AccessDenied();
/**
* @dev Error indicating that the lengths of two related arrays (e.g., recipients and rates) do not match.
*/
error ArraysLengthMismatch();
/**
* @dev Error indicating that the sum of rates does not meet the expected total (e.g., 10000 for 100% in basis points).
*/
error IncorrectRates();
/**
* @notice Gets the unique identifier for the role allowed to call fee claiming functions.
* @return The identifier for the claim fees caller role.
*/
function CLAIM_FEES_CALLER_ROLE() external view returns (bytes32);
/**
* @notice Gets the unique identifier for the role that can create new fee vaults.
* @return The identifier for the whitelisted creator role.
*/
function WHITELISTED_CREATOR_ROLE() external view returns (bytes32);
/**
* @notice Gets the unique identifier for the role responsible for fee vault administration.
* @return The identifier for the fees vault administrator role.
*/
function FEES_VAULT_ADMINISTRATOR_ROLE() external view returns (bytes32);
/**
* @notice Retrieves the distribution configuration for a specific fees vault.
* @param feesVault_ The address of the fees vault.
* @return toGaugeRate The rate at which fees are distributed to the gauge.
* @return recipients The addresses of the recipients.
* @return rates The rates at which fees are distributed to the recipients.
*/
function getDistributionConfig(
address feesVault_
) external view returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates);
/**
* @notice Retrieves the distribution configuration for a specific creator.
* @param creator_ The address of the creator.
* @return toGaugeRate The rate at which fees are distributed to the gauge.
* @return recipients The addresses of the recipients.
* @return rates The rates at which fees are distributed to the recipients.
*/
function creatorDistributionConfig(
address creator_
) external view returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates);
/**
* @notice Returns the default distribution configuration used by the factory.
* @return toGaugeRate The default rate at which fees are distributed to the gauge.
* @return recipients The default addresses of the recipients.
* @return rates The default rates at which fees are distributed to the recipients.
*/
function defaultDistributionConfig() external view returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates);
/**
* @notice Returns the custom distribution configuration for a specified fees vault.
* @param feesVault_ The address of the fees vault.
* @return toGaugeRate The rate at which fees are distributed to the gauge.
* @return recipients The addresses of the recipients.
* @return rates The rates at which fees are distributed to the recipients.
*/
function customDistributionConfig(
address feesVault_
) external view returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates);
/**
* @notice Checks if a fees vault has a custom configuration.
* @param feesVault_ The address of the fees vault to check.
* @return True if the fees vault has a custom configuration, false otherwise.
*/
function isCustomConfig(address feesVault_) external view returns (bool);
/**
* @notice Returns the current voter address used in fee vaults.
* @return The address of the current voter.
*/
function voter() external view returns (address);
/**
* @notice Returns the current fees vault implementation address.
* @return The address of the current fees vault implementation.
*/
function feesVaultImplementation() external view returns (address);
/**
* @notice Changes the implementation of the fees vault used by all vaults.
* @param implementation_ The new fees vault implementation address.
*/
function changeImplementation(address implementation_) external;
/**
* @notice Retrieves the creator address for a specific fees vault.
* @param feesVault_ The address of the fees vault.
* @return The address of the creator associated with the specified fees vault.
*/
function getFeesVaultCreator(address feesVault_) external view returns (address);
/**
* @dev Sets the address used for voting in the fee vaults. Only callable by the contract owner.
*
* @param voter_ The new voter address to be set.
*/
function setVoter(address voter_) external;
/**
* @notice Sets a custom distribution configuration for a specific fees vault.
* @param feesVault_ The address of the fees vault to configure.
* @param config_ The custom distribution configuration to apply.
*/
function setCustomDistributionConfig(address feesVault_, DistributionConfig memory config_) external;
/**
* @notice Sets a default distribution configuration for a fees vaults.
* @param config_ The distribution configuration to apply.
*/
function setDefaultDistributionConfig(DistributionConfig memory config_) external;
/**
* @notice Sets a custom distribution configuration for a specific creator.
* @param creator_ The address of the creator of fees vaults.
* @param config_ The custom distribution configuration to apply.
*/
function setDistributionConfigForCreator(address creator_, DistributionConfig memory config_) external;
/**
* @notice Changes the creator for multiple fees vaults.
* @param creator_ The new creator address.
* @param feesVaults_ The array of fees vault addresses.
*/
function changeCreatorForFeesVaults(address creator_, address[] calldata feesVaults_) external;
}
@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Enumerable is IERC721 {
/**
* @dev Returns the total amount of tokens stored by the contract.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns a token ID owned by `owner` at a given `index` of its token list.
* Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
/**
* @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
* Use along with {totalSupply} to enumerate all tokens.
*/
function tokenByIndex(uint256 index) external view returns (uint256);
}
contracts/lute/interfaces/IManagedNFTStrategy.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title IManagedNFTStrategy
* @dev Interface for strategies managing NFTs ,
* participate in voting, and claim rewards.
*/
interface IManagedNFTStrategy {
/**
* @dev Emitted when the name of the strategy is changed.
* @param newName The new name that has been set for the strategy.
*/
event SetName(string newName);
/**
* @dev Emitted when the description of the strategy is changed.
* @param newDescription The new description that has been set for the strategy.
*/
event SetDescription(string newDescription);
/**
* @dev Emitted when the description of the strategy is changed.
* @param newCreator The new description that has been set for the strategy.
*/
event SetCreator(string newCreator);
/**
* @dev Emitted when a new managed NFT is successfully attached to the strategy.
* @param managedTokenId The ID of the managed NFT that has been attached.
*/
event AttachedManagedNFT(uint256 indexed managedTokenId);
/**
* @notice Called when an NFT is attached to a strategy.
* @dev Allows the strategy to perform initial setup or balance tracking when an NFT is first attached.
* @param tokenId The ID of the NFT being attached.
* @param userBalance The balance of governance tokens associated with the NFT at the time of attachment.
*/
function onAttach(uint256 tokenId, uint256 userBalance) external;
/**
* @notice Called when an NFT is detached from a strategy.
* @dev Allows the strategy to clean up or update records when an NFT is removed.
* @param tokenId The ID of the NFT being detached.
* @param userBalance The remaining balance of governance tokens associated with the NFT at the time of detachment.
*/
function onDettach(uint256 tokenId, uint256 userBalance) external returns (uint256 lockedRewards);
/**
* @notice Gets the address of the managed NFT manager contract.
* @return The address of the managed NFT manager.
*/
function managedNFTManager() external view returns (address);
/**
* @notice Gets the address of the voting escrow contract used for locking governance tokens.
* @return The address of the voting escrow contract.
*/
function votingEscrow() external view returns (address);
/**
* @notice Gets the address of the voter contract that coordinates governance actions.
* @return The address of the voter contract.
*/
function voter() external view returns (address);
/**
* @notice Retrieves the name of the strategy.
* @return A string representing the name of the strategy.
*/
function name() external view returns (string memory);
/**
* @notice Retrieves the creator name of the strategy.
* Empty string by default
* @return A string representing the name of the strategy.
*/
function creator() external view returns (string memory);
/**
* @notice Retrieves the description of the strategy.
* Empty string by default
* @return A string representing the name of the strategy.
*/
function description() external view returns (string memory);
/**
* @notice Retrieves the ID of the managed token.
* @return The token ID used by the strategy.
*/
function managedTokenId() external view returns (uint256);
/**
* @notice Submits a governance vote on behalf of the strategy.
* @param poolVote_ An array of addresses representing the pools to vote on.
* @param weights_ An array of weights corresponding to each pool vote.
*/
function vote(address[] calldata poolVote_, uint256[] calldata weights_) external;
/**
* @notice Claims rewards allocated to the managed NFTs from specified gauges.
* @param gauges_ An array of addresses representing the gauges from which rewards are to be claimed.
*/
function claimRewards(address[] calldata gauges_) external;
/**
* @notice Claims bribes allocated to the managed NFTs for specific tokens and pools.
* @param bribes_ An array of addresses representing the bribe pools.
* @param tokens_ An array of token addresses for each bribe pool where rewards can be claimed.
*/
function claimBribes(address[] calldata bribes_, address[][] calldata tokens_) external;
/**
* @notice Attaches a specific managed NFT to this strategy, setting up necessary governance or reward mechanisms.
* @dev This function can only be called by administrators. It sets the `managedTokenId` and ensures that the token is
* valid and owned by this contract. Emits an `AttachedManagedNFT` event upon successful attachment.
* @param managedTokenId_ The token ID of the NFT to be managed by this strategy.
* throws AlreadyAttached if the strategy is already attached to a managed NFT.
* throws IncorrectManagedTokenId if the provided token ID is not managed or not owned by this contract.
*/
function attachManagedNFT(uint256 managedTokenId_) external;
/**
* @notice Returns whether detachment is currently time-locked and the active window bounds.
* @dev
* - `epochStart` is implementation-defined (e.g., aligned to the start of the current week).
* - The effective lock duration may be the per-strategy override or the manager default.
* @return locked True if detachment is currently blocked by the time-lock window.
* @return epochStart The epoch start timestamp used to compute the lock window.
* @return lockEnd The timestamp when detachment becomes allowed (end of the lock window).
*/
function dettachLockWindowInfo()
external
view
returns (
bool locked,
uint256 epochStart,
uint256 lockEnd
);
/**
* @notice Per-strategy override for the detachment lock duration (in seconds).
* @dev If this value is zero, the strategy MUST fall back to the manager’s
* {defaultDetachmentLockDuration()}.
* @return duration The configured per-strategy duration (0 = use manager default).
*/
function detachmentLockDuration() external view returns (uint256 duration);
}
contracts/core/interfaces/IVeLuteDistributor.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title Interface for the VeLuteDistributor Contract
* @notice This interface outlines the methods and events for distributing veLute tokens via an airdrop mechanism.
* @dev The contract locks LUTE tokens in the Voting Escrow to create veLute on behalf of recipients.
*/
interface IVeLuteDistributor {
/**
* @notice Struct containing the details required to airdrop LUTE and create veLute.
* @param recipient The address that will receive the newly created veLute tokens.
* @param withPermanentLock Specifies if the created veLute position should be permanently locked.
* @param lockDuration The duration (in seconds) for which the LUTE tokens will be locked for veLute.
* @param amount The amount of LUTE tokens to be locked, which in turn mints veLute.
* @param managedTokenIdForAttach The managed token ID to which veLute is attached, if applicable.
*/
struct AidropRow {
address recipient;
bool withPermanentLock;
uint256 lockDuration;
uint256 amount;
uint256 managedTokenIdForAttach;
}
/**
* @notice Emitted when whitelisting statuses for multiple reasons are set.
* @param reasons An array of string descriptions used as "reasons" for an airdrop.
* @param isWhitelisted A corresponding array of boolean values indicating whether each reason is whitelisted.
*/
event SetWhitelistReasons(string[] reasons, bool[] isWhitelisted);
/**
* @notice Emitted after a successful airdrop operation to multiple recipients.
* @param caller The address that initiated the airdrop distribution.
* @param reason A whitelisted reason describing the purpose of this airdrop.
* @param totalDistributionSum The total amount of LUTE tokens distributed (locked) across all recipients in this batch.
*/
event AidropVeLuteTotal(address indexed caller, string reason, uint256 totalDistributionSum);
/**
* @notice Emitted after a single recipient successfully receives veLute.
* @param recipient The address receiving the newly created veLute tokens.
* @param reason A whitelisted reason describing the purpose of this airdrop.
* @param tokenId The ID of the veLute token created for the recipient.
* @param amount The amount of LUTE tokens locked on behalf of the recipient.
*/
event AirdropVeLute(address indexed recipient, string reason, uint256 tokenId, uint256 amount);
/**
* @notice Emitted when tokens are recovered by an authorized role (e.g., owner).
* @param token The address of the token that was recovered.
* @param recoverAmount The amount of tokens recovered.
*/
event RecoverToken(address indexed token, uint256 indexed recoverAmount);
/**
* @notice Checks if a given reason is whitelisted.
* @param reason_ The reason string to verify.
* @return True if the reason is whitelisted, false otherwise.
*/
function isWhitelistedReason(string memory reason_) external view returns (bool);
/**
* @notice Updates the whitelisting status of multiple airdrop reasons.
* @param reasons_ An array of reasons to set or unset from the whitelist.
* @param isWhitelisted_ A matching array of booleans indicating whether each reason is whitelisted.
* @dev Emitted via {SetWhitelistReasons}.
*/
function setWhitelistReasons(string[] calldata reasons_, bool[] calldata isWhitelisted_) external;
/**
* @notice Allows the holder of `_WITHDRAWER_ROLE` to recover tokens from this contract.
* @dev
* - This can be used to retrieve any ERC20 token that was mistakenly sent to this contract.
* @param token_ The address of the token to recover.
* @param recoverAmount_ The amount of tokens to recover.
* @custom:emits RecoverToken
*/
function recoverTokens(address token_, uint256 recoverAmount_) external;
/**
* @notice Distributes veLute tokens to specified recipients by locking LUTE tokens in the Voting Escrow contract.
* @dev
* - Requires the caller to have the `_DISTRIBUTOR_ROLE`.
* - Verifies that `reason_` is whitelisted. If not, reverts with {NotWhitelistedReason}.
* - Calculates the total sum of LUTE tokens needed. If the contract does not have enough, reverts with {InsufficientBalance}.
* - Locks LUTE in the Voting Escrow for each recipient, creating veLute positions.
* - Emits {AirdropVeLuteTotal} after distributing to all recipients in this batch.
* - Emits {AirdropVeLute} for each individual recipient.
* @param reason_ A whitelisted string describing the airdrop reason.
* @param rows_ An array of AirdropRow structs that specify each recipient, lock duration, amount, etc.
*/
function distributeVeLute(string memory reason_, AidropRow[] calldata rows_) external;
}
@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolImmutables.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that never changes
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolImmutables {
/// @notice The Algebra factory contract, which must adhere to the IAlgebraFactory interface
/// @return The contract address
function factory() external view returns (address);
/// @notice The first of the two tokens of the pool, sorted by address
/// @return The token contract address
function token0() external view returns (address);
/// @notice The second of the two tokens of the pool, sorted by address
/// @return The token contract address
function token1() external view returns (address);
/// @notice The maximum amount of position liquidity that can use any tick in the range
/// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
/// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
/// @return The max amount of liquidity per tick
function maxLiquidityPerTick() external view returns (uint128);
}
@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.3) (proxy/transparent/ProxyAdmin.sol)
pragma solidity ^0.8.0;
import "./TransparentUpgradeableProxy.sol";
import "../../access/Ownable.sol";
/**
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
* explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
*/
contract ProxyAdmin is Ownable {
/**
* @dev Returns the current implementation of `proxy`.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function getProxyImplementation(ITransparentUpgradeableProxy proxy) public view virtual returns (address) {
// We need to manually run the static call since the getter cannot be flagged as view
// bytes4(keccak256("implementation()")) == 0x5c60da1b
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
require(success);
return abi.decode(returndata, (address));
}
/**
* @dev Returns the current admin of `proxy`.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function getProxyAdmin(ITransparentUpgradeableProxy proxy) public view virtual returns (address) {
// We need to manually run the static call since the getter cannot be flagged as view
// bytes4(keccak256("admin()")) == 0xf851a440
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
require(success);
return abi.decode(returndata, (address));
}
/**
* @dev Changes the admin of `proxy` to `newAdmin`.
*
* Requirements:
*
* - This contract must be the current admin of `proxy`.
*/
function changeProxyAdmin(ITransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
proxy.changeAdmin(newAdmin);
}
/**
* @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function upgrade(ITransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
proxy.upgradeTo(implementation);
}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
* {TransparentUpgradeableProxy-upgradeToAndCall}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function upgradeAndCall(
ITransparentUpgradeableProxy proxy,
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
}
}
@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165Upgradeable {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
@openzeppelin/contracts/utils/math/Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
contracts/core/TokenPublicRaiseUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {ITokenPublicRaise} from "./interfaces/ITokenPublicRaise.sol";
/**
* @title TokenPublicRaiseUpgradeable
* @notice Fixed-rate public raise that accepts native currency and accounts purchased token amounts per depositor.
* @dev
* - Price is expressed as `tokenPricePerOneNative` = tokens per 1e18 native units (wei).
* Example: if 1 ETH buys 1,000 tokens with 18 decimals, set `tokenPricePerOneNative = 1000e18`.
* - Enforces a global cap (`totalDepositCap`) and a per-user cap (`maxDepositAmount`).
* - Tracks per-user deposited native value (`userDeposited`) and purchased tokens (`userTokensAllocated`).
* - Uses {Ownable2StepUpgradeable} for admin operations and {ReentrancyGuardUpgradeable} to protect deposit paths.
*
* Upgradeability:
* - The implementation constructor disables initializers. Use {initialize} once on the proxy.
*/
contract TokenPublicRaiseUpgradeable is ITokenPublicRaise, Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
/**
* @notice Destination address to receive collected native funds.
*/
address public treasury;
/**
* @notice Inclusive sale start timestamp.
*/
uint256 public startTimestamp;
/**
* @notice Inclusive sale end timestamp.
*/
uint256 public endTimestamp;
/**
* @notice Suggested minimum amount per deposit transaction (native units).
*/
uint256 public minDepositAmount;
/**
* @notice Maximum total amount a single user is allowed to deposit (native units).
*/
uint256 public maxDepositAmount;
/**
* @notice Global cap for all deposits combined (native units).
*/
uint256 public totalDepositCap;
/**
* @notice Price: number of sale tokens received per 1e18 units of native currency.
* @dev tokensOut = `msg.value * tokenPricePerOneNative / 1e18`.
*/
uint256 public tokenPricePerOneNative;
/**
* @notice Total native currency deposited into the raise so far.
*/
uint256 public totalDeposited;
/**
* @notice Per-user native currency deposited into the raise.
*/
mapping(address user => uint256) public userDeposited;
/**
* @notice Per-user amount of tokens accounted as purchased.
* @dev This contract only accounts purchases; it does not transfer sale tokens.
*/
mapping(address user => uint256) public userTokensAllocated;
/**
* @dev Reverts when `msg.value` is below the minimum accepted amount for a context.
*/
error DepositBelowMin();
/**
* @dev Reverts when a user attempts to exceed the per-user cap.
*/
error DepositAboveMax();
/**
* @dev Reverts when the attempted deposit would exceed the global cap.
*/
error DepositCapReached();
/**
* @dev Reverts on zero amount where non-zero is required.
*/
error AmountZero();
/**
* @dev Reverts on zero address where non-zero is required.
*/
error AddressZero();
/**
* @dev Reverts if the raise is not currently active (not started or already finished).
*/
error RaiseNotActive();
/**
* @dev Reverts if timestamps are invalid (e.g., end <= start).
*/
error InvalidRaiseWindow();
/**
* @dev Reverts if provided limit values are inconsistent (e.g., min > max).
*/
error InvalidDepositLimits();
/**
* @dev Reverts if a withdrawal is attempted before the raise has ended.
*/
error RaiseNotEnded();
/**
* @dev Reverts if native currency transfer via call failed.
*/
error NativeTransferFailed();
/**
* @dev Disable initializers on the implementation instance.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the raise configuration.
* @dev Callable once on the proxy. Emits {ExchangeRateUpdated}, {TreasuryUpdated},
* {DepositLimitsUpdated}, and {RaiseWindowUpdated}.
* @param startTimestamp_ Inclusive sale start timestamp.
* @param endTimestamp_ Inclusive sale end timestamp.
* @param minDepositAmount_ Suggested minimum per-user deposit (native units).
* @param maxDepositAmount_ Maximum per-user total deposit (native units).
* @param totalDepositCap_ Global cap across all users (native units).
* @param tokenPricePerOneNative_ Tokens per 1e18 native units.
* @param treasury_ Destination address for collected native funds.
*/
function initialize(
uint256 startTimestamp_,
uint256 endTimestamp_,
uint256 minDepositAmount_,
uint256 maxDepositAmount_,
uint256 totalDepositCap_,
uint256 tokenPricePerOneNative_,
address treasury_
) external initializer {
__Ownable_init();
__Ownable2Step_init();
__ReentrancyGuard_init();
_setTreasury(treasury_);
_setDepositLimits(minDepositAmount_, maxDepositAmount_, totalDepositCap_);
_setRaiseWindow(startTimestamp_, endTimestamp_);
_setTokenPricePerOneNative(tokenPricePerOneNative_);
}
/**
* @notice Deposits native currency into the raise at the current fixed price.
* @dev
* - Reverts if the raise is inactive.
* - Caps the accepted amount by both global and per-user remaining allowances.
* - Accounts purchased tokens using `tokenPricePerOneNative`.
* - Emits {Deposited}.
*/
function deposit() external payable nonReentrant {
_deposit();
}
/**
* @notice Fallback deposit path to accept plain native transfers during the active window.
* @dev Mirrors {deposit}. Emits {Deposited}.
*/
receive() external payable nonReentrant {
_deposit();
}
/**
* @notice Withdraws the entire native balance to the treasury after the raise has ended.
* @dev
* - Only callable by the owner.
* - Reverts if `block.timestamp <= endTimestamp` (raise not finished yet).
* - Reverts if there is no balance to withdraw.
* - Uses checks-effects-interactions and {nonReentrant}.
* - Emits {TreasuryWithdrawn}.
*/
function withdrawToTreasury() external onlyOwner nonReentrant {
if (block.timestamp <= endTimestamp) revert RaiseNotEnded();
uint256 amount = address(this).balance;
_revertIfZero(amount);
(bool ok, ) = payable(treasury).call{value: amount}("");
if (!ok) revert NativeTransferFailed();
emit TreasuryWithdrawn(treasury, amount);
}
/**
* @notice Updates the fixed exchange rate (tokens per 1e18 native units).
* @dev
* Requirements:
* - Caller must be the owner.
* - `tokenPricePerOneNative_` must be non-zero.
*
* Emits:
* - {ExchangeRateUpdated}.
*
* Reverts:
* - {AmountZero} if `tokenPricePerOneNative_` is zero.
* @param tokenPricePerOneNative_ New price (tokens per 1e18 native units).
*/
function setTokenPricePerOneNative(uint256 tokenPricePerOneNative_) external onlyOwner {
_setTokenPricePerOneNative(tokenPricePerOneNative_);
}
/**
* @notice Updates the treasury address.
* @dev Only callable by the owner. Emits {TreasuryUpdated}.
* @param treasury_ The new treasury address.
*/
function setTreasury(address treasury_) external onlyOwner {
_setTreasury(treasury_);
}
/**
* @notice Updates min/per-user/global deposit limits.
* @dev Only callable by the owner. Emits {DepositLimitsUpdated}.
* @param minDepositAmount_ Suggested minimum per-tx deposit (native units).
* @param maxDepositAmount_ Maximum per-user total deposit (native units).
* @param totalDepositCap_ Global cap across all users (native units).
*/
function setDepositLimits(uint256 minDepositAmount_, uint256 maxDepositAmount_, uint256 totalDepositCap_) external onlyOwner {
_setDepositLimits(minDepositAmount_, maxDepositAmount_, totalDepositCap_);
}
/**
* @notice Updates the start/end timestamps of the raise window.
* @dev Only callable by the owner. Emits {RaiseWindowUpdated}.
* @param startTimestamp_ Inclusive sale start timestamp.
* @param endTimestamp_ Inclusive sale end timestamp.
*/
function setRaiseWindow(uint256 startTimestamp_, uint256 endTimestamp_) external onlyOwner {
_setRaiseWindow(startTimestamp_, endTimestamp_);
}
/**
* @notice Returns the maximum additional native amount `user_` can still deposit.
* @dev
* - If the raise is inactive, returns 0.
* - Computed as `min(globalRemaining, perUserRemaining)`.
* - Uses saturating logic for `globalRemaining` and `perUserRemaining`.
* @param user_ The user address to query.
* @return maxAllowed The maximum additional deposit amount in native units.
*/
function maxDeposit(address user_) public view returns (uint256 maxAllowed) {
if (!isRaiseActive()) {
return 0;
}
uint256 globalRemaining = totalDeposited >= totalDepositCap ? 0 : (totalDepositCap - totalDeposited);
uint256 already = userDeposited[user_];
uint256 userRemaining = maxDepositAmount > already ? (maxDepositAmount - already) : 0;
maxAllowed = globalRemaining < userRemaining ? globalRemaining : userRemaining;
}
/**
* @notice Returns whether the raise window is currently active.
* @dev Active iff `block.timestamp` is within [startTimestamp, endTimestamp].
*/
function isRaiseActive() public view returns (bool) {
return block.timestamp >= startTimestamp && block.timestamp <= endTimestamp;
}
/**
* @notice Returns a compact snapshot of global config/state and the user’s counters.
* @param user_ The user address to query.
* @return active Whether the raise is active.
* @return start Start timestamp.
* @return end End timestamp.
* @return min Suggested minimum per-tx deposit.
* @return max Maximum per-user deposit.
* @return globalCap Global deposit cap.
* @return price Tokens per 1e18 native units.
* @return totalIn Total native deposited.
* @return userIn User’s native deposited.
* @return userOut User’s accounted purchased tokens.
* @return userMaxDeposit User's max deposit amount available for deposit
*/
function getInfo(
address user_
)
external
view
returns (
bool active,
uint256 start,
uint256 end,
uint256 min,
uint256 max,
uint256 globalCap,
uint256 price,
uint256 totalIn,
uint256 userIn,
uint256 userOut,
uint256 userMaxDeposit
)
{
active = isRaiseActive();
start = startTimestamp;
end = endTimestamp;
min = minDepositAmount;
max = maxDepositAmount;
globalCap = totalDepositCap;
price = tokenPricePerOneNative;
totalIn = totalDeposited;
if (user_ != address(0)) {
userIn = userDeposited[user_];
userOut = userTokensAllocated[user_];
userMaxDeposit = maxDeposit(user_);
}
}
/**
* @dev Core deposit logic shared by {deposit} and {receive}.
* Emits {Deposited}.
*
* Reverts:
* - {RaiseNotActive} if not active.
* - {DepositCapReached} if global cap is already reached.
* - {DepositAboveMax} if the sender already reached the per-user cap.
* - {AmountZero} if the accepted amount or tokens out evaluates to zero.
*/
function _deposit() internal {
if (!isRaiseActive()) {
revert RaiseNotActive();
}
uint256 amount = msg.value;
_revertIfZero(amount);
uint256 globalRemaining = totalDeposited >= totalDepositCap ? 0 : (totalDepositCap - totalDeposited);
if (globalRemaining == 0) revert DepositCapReached();
uint256 maxLimit = maxDeposit(_msgSender());
if (maxLimit == 0 || amount > maxLimit) revert DepositAboveMax();
if (amount + userDeposited[_msgSender()] < minDepositAmount) revert DepositBelowMin();
uint256 tokensOut = (amount * tokenPricePerOneNative) / 1e18;
_revertIfZero(tokensOut);
totalDeposited += amount;
userDeposited[_msgSender()] += amount;
userTokensAllocated[_msgSender()] += tokensOut;
emit Deposited(_msgSender(), amount, tokensOut);
}
/**
* @dev Internal setter for the treasury address.
* Emits {TreasuryUpdated}.
* @param treasury_ Non-zero treasury address.
*/
function _setTreasury(address treasury_) internal {
_revertIfZero(treasury_);
treasury = treasury_;
emit TreasuryUpdated(treasury_);
}
/**
* @dev Internal setter for deposit limits.
* Emits {DepositLimitsUpdated}.
* @param minDepositAmount_ Suggested minimum per-tx deposit.
* @param maxDepositAmount_ Maximum per-user deposit.
* @param totalDepositCap_ Global cap across all users.
*
* Reverts:
* - {AmountZero} if any max or total value is zero (as checked).
* - {InvalidDepositLimits} if `minDepositAmount_ > maxDepositAmount_`.
*/
function _setDepositLimits(uint256 minDepositAmount_, uint256 maxDepositAmount_, uint256 totalDepositCap_) internal {
_revertIfZero(maxDepositAmount_);
_revertIfZero(totalDepositCap_);
if (minDepositAmount_ > maxDepositAmount_) revert InvalidDepositLimits();
minDepositAmount = minDepositAmount_;
maxDepositAmount = maxDepositAmount_;
totalDepositCap = totalDepositCap_;
emit DepositLimitsUpdated(minDepositAmount_, maxDepositAmount_, totalDepositCap_);
}
/**
* @dev Internal setter for the exchange rate (tokens per 1e18 native units).
*
* Requirements:
* - `tokenPricePerOneNative_` must be non-zero.
*
* Emits:
* - {ExchangeRateUpdated}.
*
* Reverts:
* - {AmountZero} if `tokenPricePerOneNative_ == 0`.
* @param tokenPricePerOneNative_ New price to set.
*/
function _setTokenPricePerOneNative(uint256 tokenPricePerOneNative_) internal {
_revertIfZero(tokenPricePerOneNative_);
tokenPricePerOneNative = tokenPricePerOneNative_;
emit ExchangeRateUpdated(tokenPricePerOneNative_);
}
/**
* @dev Internal setter for start/end timestamps.
* Emits {RaiseWindowUpdated}.
* @param startTimestamp_ Inclusive start timestamp.
* @param endTimestamp_ Inclusive end timestamp.
*
* Reverts:
* - {AmountZero} if either timestamp is zero.
* - {InvalidRaiseWindow} if `endTimestamp_ <= startTimestamp_`.
*/
function _setRaiseWindow(uint256 startTimestamp_, uint256 endTimestamp_) internal {
_revertIfZero(startTimestamp_);
_revertIfZero(endTimestamp_);
if (endTimestamp_ <= startTimestamp_) revert InvalidRaiseWindow();
startTimestamp = startTimestamp_;
endTimestamp = endTimestamp_;
emit RaiseWindowUpdated(startTimestamp_, endTimestamp_);
}
/**
* @dev Reverts if `addr_` is the zero address.
* @param addr_ Address to check.
*/
function _revertIfZero(address addr_) internal pure {
if (addr_ == address(0)) revert AddressZero();
}
/**
* @dev Reverts if `amount_` is zero.
* @param amount_ Amount to check.
*/
function _revertIfZero(uint256 amount_) internal pure {
if (amount_ == 0) revert AmountZero();
}
}
contracts/dexV2/UniswapV2PartialRouter.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import "./RouterV2.sol";
import {IUniswapV2PartialRouter} from "./interfaces/IUniswapV2PartialRouter.sol";
contract UniswapV2PartialRouter is RouterV2, IUniswapV2PartialRouter {
constructor(address _factory, address _wETH) RouterV2(_factory, _wETH) {}
function WETH() public view returns (address) {
return address(wETH);
}
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external override returns (uint amountA, uint amountB, uint liquidity) {
return addLiquidity(tokenA, tokenB, false, amountADesired, amountBDesired, amountAMin, amountBMin, to, deadline);
}
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable override returns (uint amountToken, uint amountETH, uint liquidity) {
return addLiquidityETH(token, false, amountTokenDesired, amountTokenMin, amountETHMin, to, deadline);
}
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external override returns (uint amountA, uint amountB) {
return removeLiquidity(tokenA, tokenB, false, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external override returns (uint amountToken, uint amountETH) {
return removeLiquidityETH(token, false, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external override returns (uint amountA, uint amountB) {
return removeLiquidityWithPermit(tokenA, tokenB, false, liquidity, amountAMin, amountBMin, to, deadline, approveMax, v, r, s);
}
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external override returns (uint amountToken, uint amountETH) {
return removeLiquidityETHWithPermit(token, false, liquidity, amountTokenMin, amountETHMin, to, deadline, approveMax, v, r, s);
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external override returns (uint[] memory amounts) {
return swapExactTokensForTokens(amountIn, amountOutMin, pathsToVolatilityRoutes(path), to, deadline);
}
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable override returns (uint[] memory amounts) {
return swapExactETHForTokens(amountOutMin, pathsToVolatilityRoutes(path), to, deadline);
}
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external override returns (uint[] memory amounts) {
return swapExactTokensForETH(amountIn, amountOutMin, pathsToVolatilityRoutes(path), to, deadline);
}
function getReserves(address tokenA, address tokenB) external view override returns (uint reserveA, uint reserveB) {
return getReserves(tokenA, tokenB, false);
}
function getAmountsOut(uint amountIn, address[] calldata path) external view override returns (uint[] memory amounts) {
return getAmountsOut(amountIn, pathsToVolatilityRoutes(path));
}
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external override returns (uint amountETH) {
(, amountETH) = removeLiquidityETHSupportingFeeOnTransferTokens(
token,
false,
liquidity,
amountTokenMin,
amountETHMin,
to,
deadline
);
}
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external override returns (uint amountETH) {
(, amountETH) = removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
token,
false,
liquidity,
amountTokenMin,
amountETHMin,
to,
deadline,
approveMax,
v,
r,
s
);
}
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external override {
return swapExactTokensForTokensSupportingFeeOnTransferTokens(amountIn, amountOutMin, pathsToVolatilityRoutes(path), to, deadline);
}
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable override {
swapExactETHForTokensSupportingFeeOnTransferTokens(amountOutMin, pathsToVolatilityRoutes(path), to, deadline);
}
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external override {
swapExactTokensForETHSupportingFeeOnTransferTokens(amountIn, amountOutMin, pathsToVolatilityRoutes(path), to, deadline);
}
function pathsToVolatilityRoutes(address[] memory path) public pure returns (route[] memory) {
route[] memory routes = new route[](path.length - 1);
for (uint i; i < path.length - 1; ) {
routes[i] = route({from: path[i], to: path[i + 1], stable: false});
unchecked {
i++;
}
}
return routes;
}
}
contracts/integration/ManualLUTEPriceProvider.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {IPriceProvider} from "./interfaces/IPriceProvider.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title ManualLUTEPriceProvider
* @notice This contract allows for manually setting the price of the LUTE token in USD.
*
* It is intended for use cases where the price needs to be controlled or set by an authorized account.
* Unlike automatic price feeds, this contract requires an administrator to provide the price.
* *
* Inherits from `Ownable` for access control, allowing only the owner to set the price.
*/
contract ManualLUTEPriceProvider is IPriceProvider, Ownable {
/**
* @dev Emitted when the price is updated.
* @param oldPrice The previous price of 1 USD in LUTE tokens.
* @param newPrice The new price of 1 USD in LUTE tokens.
*/
event SetPrice(uint256 indexed oldPrice, uint256 indexed newPrice);
/**
* @dev The current price of 1 USD in LUTE tokens.
*/
uint256 public price;
/**
* @dev Thrown when attempting to retrieve the price before it has been set.
*/
error PriceNotSetup();
/**
* @notice Initializes the contract with the given initial price.
* @dev Disables further initializers to prevent re-initialization.
* @param price_ The initial price of 1 USD in LUTE tokens.
*/
constructor(uint256 price_) Ownable() {
price = price_;
}
/**
* @notice Sets the price of 1 USD in LUTE tokens.
* @dev Only callable by the owner of the contract.
* @param price_ The new price of 1 USD in LUTE tokens.
*/
function setLutePrice(uint256 price_) external onlyOwner {
uint256 oldPrice = price;
price = price_;
emit SetPrice(oldPrice, price_);
}
/**
* @notice Retrieves the current price of 1 USD in LUTE tokens.
* @dev Reverts if the price has not been set.
* @return The price of 1 USD in LUTE tokens.
*/
function getUsdToLUTEPrice() external view override returns (uint256) {
uint256 priceCache = price;
if (priceCache == 0) {
revert PriceNotSetup();
}
return priceCache;
}
}
contracts/vesting/MinimalLinearVestingUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IMinimalLinearVesting} from "./interfaces/IMinimalLinearVesting.sol";
/**
* @title MinimalLinearVestingUpgradeable
* @dev This contract manages linear token vesting with claim functionality.
* The contract allows the owner to set wallet allocations, update vesting parameters,
* and users can claim their vested tokens over time.
*/
contract MinimalLinearVestingUpgradeable is IMinimalLinearVesting, OwnableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @notice The token address for the vested token.
* @dev This is the ERC20 token that will be vested and claimed by users.
*/
address public override token;
/**
* @notice The timestamp when the vesting period starts.
* @dev Vesting will begin at this timestamp, and users will be able to claim tokens accordingly.
*/
uint256 public override startTimestamp;
/**
* @notice The duration of the vesting period in seconds.
* @dev This defines how long the vesting period lasts after the `startTimestamp`.
*/
uint256 public override duration;
/**
* @notice The total amount of tokens that have been allocated to all wallets.
* @dev This value represents the sum of all tokens allocated across all wallets,
* which is used to ensure that the contract maintains enough tokens to satisfy all allocations.
*/
uint256 public totalAllocated;
/**
* @notice Mapping that stores the token allocation for each wallet.
*/
mapping(address wallet => uint256) public override allocation;
/**
* @notice Mapping that stores the claimed amount of tokens for each wallet.
*/
mapping(address wallet => uint256) public override claimed;
/**
* @notice Thrown when an action is not allowed during the claim phase.
* @dev This error is triggered when trying to perform restricted actions after the vesting period has started.
*/
error NotAvailableDuringClaimPhase();
/**
* @notice Thrown when the claim phase has not started yet.
* @dev This error occurs when a user attempts to claim tokens before the vesting start time.
*/
error ClaimPhaseNotStarted();
/**
* @notice Thrown when the amount available for claim is zero.
* @dev This error is triggered when a user attempts to claim tokens but has no tokens available for claim.
*/
error ZeroClaimAmount();
/**
* @notice Thrown when the lengths of arrays provided do not match.
*/
error ArrayLengthMismatch();
/**
* @dev Modifier to restrict actions that cannot be performed during the claim phase.
* Reverts with `NotAvailableDuringClaimPhase` if vesting has already started.
*/
modifier onlyNotDuringClaimPhase() {
if (startTimestamp > 0 && startTimestamp < block.timestamp) {
revert NotAvailableDuringClaimPhase();
}
_;
}
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the vesting contract.
* @dev This function can only be called once, during the initialization phase.
* @param token_ The address of the token to be vested.
* @param startTimestamp_ The timestamp when vesting starts.
* @param duration_ The duration of the vesting period in seconds.
*/
function initialize(address token_, uint256 startTimestamp_, uint256 duration_) external initializer {
__Ownable_init();
token = token_;
startTimestamp = startTimestamp_;
duration = duration_;
emit UpdateVestingParams(startTimestamp_, duration_);
}
/**
* @notice Sets the token allocation for multiple wallets.
* @dev Can only be called by the owner and before the vesting has started.
* Reverts with `NotAvailableDuringClaimPhase` if vesting has started.
* Reverts with `ArrayLengthMismatch` if the lengths of `wallets_` and `amounts_` do not match or if they are empty.
* The total allocated amount is adjusted based on the changes in the wallet allocations.
* If the current balance exceeds the new allocation, the excess tokens are transferred to the owner.
* If the current balance is less than the new allocation, the owner must transfer the difference to the contract.
* @param wallets_ The array of wallet addresses.
* @param amounts_ The array of token amounts allocated to each wallet.
*/
function setWalletsAllocation(
address[] calldata wallets_,
uint256[] calldata amounts_
) external override onlyOwner onlyNotDuringClaimPhase {
if (wallets_.length != amounts_.length || wallets_.length == 0) {
revert ArrayLengthMismatch();
}
uint256 newTotalAllocated = totalAllocated;
for (uint256 i; i < wallets_.length; ) {
newTotalAllocated -= allocation[wallets_[i]];
newTotalAllocated += amounts_[i];
allocation[wallets_[i]] = amounts_[i];
unchecked {
i++;
}
}
uint256 currentBalance = IERC20Upgradeable(token).balanceOf(address(this));
if (currentBalance > newTotalAllocated) {
IERC20Upgradeable(token).safeTransfer(msg.sender, currentBalance - newTotalAllocated);
} else if (currentBalance < newTotalAllocated) {
IERC20Upgradeable(token).safeTransferFrom(msg.sender, address(this), newTotalAllocated - currentBalance);
}
totalAllocated = newTotalAllocated;
emit UpdateWalletsAllocation(wallets_, amounts_);
}
/**
* @notice Updates the vesting parameters such as the start timestamp and duration.
* @dev Can only be called by the owner.
* @param startTimestamp_ The new vesting start timestamp.
* @param duration_ The new duration of the vesting in seconds.
*/
function setVestingParams(uint256 startTimestamp_, uint256 duration_) external override onlyOwner onlyNotDuringClaimPhase {
startTimestamp = startTimestamp_;
duration = duration_;
emit UpdateVestingParams(startTimestamp_, duration_);
}
/**
* @notice Allows users to claim their vested tokens.
* @dev Reverts with `ClaimPhaseNotStarted` if the vesting period has not started yet.
* Reverts with `ZeroClaimAmount` if there are no tokens available for claim.
*/
function claim() external override {
if (!isClaimPhase()) {
revert ClaimPhaseNotStarted();
}
uint256 availableForClaim = getAvailableForClaim(msg.sender);
if (availableForClaim == 0) {
revert ZeroClaimAmount();
}
claimed[msg.sender] += availableForClaim;
IERC20Upgradeable(token).safeTransfer(msg.sender, availableForClaim);
emit Claim(msg.sender, availableForClaim);
}
/**
* @notice Returns the amount of tokens available for claim for a given wallet.
* @param wallet_ The address of the wallet to check.
* @return The amount of tokens available for claim.
* @dev This function calculates the unlocked tokens based on the elapsed time and vesting schedule.
*/
function getAvailableForClaim(address wallet_) public view override returns (uint256) {
return calculateUnlockAmount(allocation[wallet_], startTimestamp, block.timestamp, duration) - claimed[wallet_];
}
/**
* @notice Returns whether the claim phase has started.
* @dev The claim phase starts when the current timestamp is greater than or equal to the `startTimestamp`.
* @return True if the claim phase has started, false otherwise.
*/
function isClaimPhase() public view override returns (bool) {
uint256 start = startTimestamp;
return start > 0 && block.timestamp >= start;
}
/**
* @notice Calculates the unlocked amount of tokens based on the vesting schedule.
* @param amount_ The total amount allocated to the wallet.
* @param startTimestamp_ The timestamp when the vesting started.
* @param currentTimestamp_ The current block timestamp.
* @param duration_ The vesting duration.
* @return The amount of unlocked tokens based on the elapsed time.
* @dev The calculation is based on the time passed since the start of the vesting period and the total duration.
*/
function calculateUnlockAmount(
uint256 amount_,
uint256 startTimestamp_,
uint256 currentTimestamp_,
uint256 duration_
) public pure returns (uint256) {
if (currentTimestamp_ < startTimestamp_) {
return 0;
}
if (currentTimestamp_ >= startTimestamp_ + duration_) {
return amount_;
}
uint256 unlockPercentage = ((currentTimestamp_ - startTimestamp_) * 1e18) / duration_;
return (amount_ * unlockPercentage) / 1e18;
}
}
@openzeppelin/contracts/access/Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
contracts/core/libraries/NumberFormatter.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
library NumberFormatter {
using Strings for uint256;
function formatNumber(uint256 number, uint8 decimals, uint8 limitFractionNumbers) internal pure returns (string memory) {
uint256 integerPart = number / 10 ** decimals;
uint256 fractionalPart = number % 10 ** decimals;
if (decimals == 0) {
return withThousandSeparators(integerPart);
}
return
string(
abi.encodePacked(
withThousandSeparators(integerPart),
".",
limitFactionNumbers(toStringWithLeadingZeros(fractionalPart, decimals), limitFractionNumbers)
)
);
}
function withThousandSeparators(uint256 value) internal pure returns (string memory) {
string memory strValue = value.toString();
bytes memory strBytes = bytes(strValue);
uint256 length = strBytes.length;
uint256 separatorCount = (length - 1) / 3;
bytes memory result = new bytes(length + separatorCount);
uint256 j = 1;
for (uint256 i; i < length; ) {
if (i != 0 && (length - i) % 3 == 0) {
result[j - 1] = ",";
unchecked {
j++;
}
}
result[j - 1] = strBytes[i];
unchecked {
j++;
i++;
}
}
return string(result);
}
function limitFactionNumbers(string memory strValue, uint8 limit) internal pure returns (string memory) {
bytes memory result = new bytes(limit);
bytes memory strBytes = bytes(strValue);
for (uint256 i; i < limit; ) {
if (strBytes.length > i) {
result[i] = strBytes[i];
} else {
result[i] = "0";
}
unchecked {
i++;
}
}
return string(result);
}
function toStringWithLeadingZeros(uint256 value, uint8 decimals) internal pure returns (string memory) {
if (decimals == 0) {
return "0";
}
string memory strValue = value.toString();
uint256 length = bytes(strValue).length;
uint256 requiredZeros = decimals > length ? decimals - length : 0;
bytes memory result = new bytes(requiredZeros + length);
for (uint256 i = 0; i < requiredZeros; i++) {
result[i] = "0";
}
for (uint256 i = 0; i < length; i++) {
result[i + requiredZeros] = bytes(strValue)[i];
}
uint256 trimIndex = result.length;
while (trimIndex > 0 && result[trimIndex - 1] == "0") {
trimIndex--;
}
if (trimIndex == 0) {
return "0";
}
bytes memory trimmedResult = new bytes(trimIndex);
for (uint256 i = 0; i < trimIndex; i++) {
trimmedResult[i] = result[i];
}
return string(trimmedResult);
}
}
contracts/dexV2/interfaces/IPairCallee.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IPairCallee {
function hook(address sender, uint amount0, uint amount1, bytes calldata data) external;
}
contracts/dexV2/interfaces/IUniswapV2PartialRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
interface IUniswapV2PartialRouter {
function WETH() external view returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getReserves(address tokenA, address tokenB) external view returns (uint reserveA, uint reserveB);
}
contracts/mocks/ERC20OwnableMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract ERC20OwnableMock is ERC20, Ownable {
uint8 internal _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
_decimals = decimals_;
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
function mint(address to_, uint256 amount_) external onlyOwner {
_mint(to_, amount_);
}
}
@openzeppelin/contracts/access/IAccessControl.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev _Available since v3.1._
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev Handles the receipt of a single ERC1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}
contracts/utils/VeNFTAPIUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {
IERC20Upgradeable,
IERC20MetadataUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol";
import "../core/interfaces/IVoter.sol";
import "../core/interfaces/IVotingEscrow.sol";
import "../dexV2/interfaces/IPairFactory.sol";
import "../dexV2/interfaces/IPair.sol";
import "../gauges/interfaces/IGauge.sol";
import "../bribes/interfaces/IBribe.sol";
import "../lute/interfaces/ISingelTokenVirtualRewarder.sol";
import "../lute/interfaces/ICompoundVeLUTEManagedNFTStrategy.sol";
import "../lute/interfaces/IManagedNFTManager.sol";
contract VeNFTAPIUpgradeable is OwnableUpgradeable {
struct pairVotes {
address pair;
uint256 weight;
}
struct veNFT {
uint8 decimals;
bool voted;
uint256 id;
uint128 amount;
uint256 voting_amount;
uint256 lockEnd;
uint256 vote_ts;
pairVotes[] votes;
address account;
address token;
string tokenSymbol;
uint256 tokenDecimals;
bool isPermanentLocked;
bool isAttachedToManagedNFT;
uint256 attachedManagedNFTTokenId;
}
struct Reward {
uint256 id;
uint256 amount;
uint8 decimals;
address pair;
address token;
address fee;
address bribe;
string symbol;
}
uint256 public constant MAX_RESULTS = 1000;
uint256 public constant MAX_PAIRS = 30;
uint256 internal constant _WEEK = 86400 * 7;
IVoter public voter;
address public underlyingToken;
mapping(address => bool) public notReward;
IVotingEscrow public ve;
address public pairAPI;
IManagedNFTManager public managedNFTManager;
struct AllPairRewards {
Reward[] rewards;
}
constructor() {
_disableInitializers();
}
function initialize(address _voter) public initializer {
__Ownable_init();
voter = IVoter(_voter);
ve = IVotingEscrow(voter.votingEscrow());
underlyingToken = IVotingEscrow(ve).token();
notReward[address(0x0)] = true;
}
function setManagedNFTManager(IManagedNFTManager managedNFTManager_) external onlyOwner {
managedNFTManager = managedNFTManager_;
}
function setVoter(address _voter) external onlyOwner {
voter = IVoter(_voter);
}
function setPairAPI(address _pairApi) external onlyOwner {
pairAPI = _pairApi;
}
function getAllNFT(uint256 _amounts, uint256 _offset) external view returns (veNFT[] memory _veNFT) {
require(_amounts <= MAX_RESULTS, "too many nfts");
_veNFT = new veNFT[](_amounts);
uint i = _offset;
address _owner;
for (i; i < _offset + _amounts; i++) {
_owner = ve.ownerOf(i);
// if id_i has owner read data
if (_owner != address(0)) {
_veNFT[i - _offset] = _getNFTFromId(i, _owner);
}
}
}
function getNFTFromId(uint256 id) external view returns (veNFT memory) {
return _getNFTFromId(id, ve.ownerOf(id));
}
function getNFTFromAddress(address _user) external view returns (veNFT[] memory venft) {
uint256 i = 0;
uint256 _id;
uint256 totNFTs = ve.balanceOf(_user);
venft = new veNFT[](totNFTs);
for (i; i < totNFTs; i++) {
_id = IERC721EnumerableUpgradeable(address(ve)).tokenOfOwnerByIndex(_user, i);
if (_id != 0) {
venft[i] = _getNFTFromId(_id, _user);
}
}
}
function _getNFTFromId(uint256 id, address _owner) internal view returns (veNFT memory venft) {
if (_owner == address(0)) {
return venft;
}
uint _totalPoolVotes = voter.poolVoteLength(id);
pairVotes[] memory votes = new pairVotes[](_totalPoolVotes);
IVotingEscrow.TokenState memory tokenState = ve.getNftState(id);
IVotingEscrow.LockedBalance memory _lockedBalance = tokenState.locked;
uint k;
uint256 _poolWeight;
address _votedPair;
for (k = 0; k < _totalPoolVotes; k++) {
_votedPair = voter.poolVote(id, k);
if (_votedPair == address(0)) {
break;
}
_poolWeight = voter.votes(id, _votedPair);
votes[k].pair = _votedPair;
votes[k].weight = _poolWeight;
}
venft.id = id;
venft.account = _owner;
venft.decimals = 1;
venft.amount = uint128(_lockedBalance.amount);
venft.voting_amount = ve.balanceOfNFT(id);
venft.lockEnd = _lockedBalance.end;
venft.vote_ts = voter.lastVotedTimestamps(id);
venft.votes = votes;
venft.token = ve.token();
venft.tokenSymbol = IERC20MetadataUpgradeable(ve.token()).symbol();
venft.tokenDecimals = IERC20MetadataUpgradeable(ve.token()).decimals();
venft.voted = tokenState.isVoted;
venft.isPermanentLocked = _lockedBalance.isPermanentLocked;
venft.isAttachedToManagedNFT = managedNFTManager.isAttachedNFT(id);
if (venft.isAttachedToManagedNFT) {
venft.attachedManagedNFTTokenId = managedNFTManager.getAttachedManagedTokenId(id);
}
}
function getNFTFromIds(uint256[] memory ids_) public view returns (veNFT[] memory veNFTs) {
veNFTs = new veNFT[](ids_.length);
for (uint256 i; i < ids_.length; i++) {
veNFTs[i] = _getNFTFromId(ids_[i], ve.ownerOf(ids_[i]));
}
}
function getLuteApr(address[] memory rewarderAddresses, uint256 epoch) public view returns (uint256[] memory aprs) {
aprs = new uint256[](rewarderAddresses.length);
for (uint256 i; i < rewarderAddresses.length; i++) {
ISingelTokenVirtualRewarder rewarder = ISingelTokenVirtualRewarder(rewarderAddresses[i]);
uint256 totalSupply = rewarder.totalSupply();
if (totalSupply > 0) {
uint256 rewardsPerEpoch = rewarder.rewardsPerEpoch(epoch);
// APR = (rewardsPerEpoch / totalSupply) * 100 * 52
aprs[i] = (rewardsPerEpoch * 1e18 * 52) / totalSupply;
}
}
}
struct PrevEpochRewardStrategyInfo {
uint256 tokenBalanceInStrategy;
uint256 tokenEpochReward;
uint256 strategyTotalSupply;
uint256 strategyEpochRewards;
}
struct AttachedVeNftInfo {
bool success;
uint256 tokenId;
uint256 attachedManagedTokenId;
uint256 currentTokenBalanceInStrategy;
uint256 currentTokenLockedRewardsBalance;
uint256 currentTotalSupply;
address strategy;
address rewarder;
PrevEpochRewardStrategyInfo prevEpochInfo;
}
function getAttachedVeNftsRewardInfo(uint256[] calldata veNftIds_) external view returns (AttachedVeNftInfo[] memory array) {
array = new AttachedVeNftInfo[](veNftIds_.length);
IManagedNFTManager managedNFTManagerCache = managedNFTManager;
IVotingEscrow votingEscrowCache = ve;
for (uint256 i; i < veNftIds_.length; ) {
uint256 tokenId = veNftIds_[i];
array[i].tokenId = tokenId;
uint256 mTokenId = managedNFTManagerCache.getAttachedManagedTokenId(tokenId);
if (mTokenId > 0) {
array[i].attachedManagedTokenId = mTokenId;
array[i].strategy = IERC721EnumerableUpgradeable(address(votingEscrowCache)).ownerOf(mTokenId);
if (array[i].strategy.code.length > 0) {
ICompoundVeLUTEManagedNFTStrategy strategy = ICompoundVeLUTEManagedNFTStrategy(array[i].strategy);
array[i].currentTokenBalanceInStrategy = strategy.balanceOf(tokenId);
array[i].currentTokenLockedRewardsBalance = strategy.getLockedRewardsBalance(tokenId);
array[i].currentTotalSupply = strategy.totalSupply();
array[i].rewarder = strategy.virtualRewarder();
ISingelTokenVirtualRewarder rewarder = ISingelTokenVirtualRewarder(array[i].rewarder);
uint256 prevEpoch = getPrevEpochTimestamp();
array[i].prevEpochInfo.tokenBalanceInStrategy = rewarder.balanceOfAt(tokenId, prevEpoch);
array[i].prevEpochInfo.strategyTotalSupply = rewarder.totalSupplyAt(prevEpoch);
array[i].prevEpochInfo.strategyEpochRewards = rewarder.rewardsPerEpoch(prevEpoch);
array[i].prevEpochInfo.tokenEpochReward = array[i].prevEpochInfo.strategyTotalSupply == 0
? 0
: (array[i].prevEpochInfo.strategyEpochRewards * array[i].prevEpochInfo.tokenBalanceInStrategy) /
array[i].prevEpochInfo.strategyTotalSupply;
array[i].success = true;
}
}
unchecked {
i++;
}
}
}
function getPrevEpochTimestamp() public view returns (uint256) {
return (block.timestamp / _WEEK) * _WEEK - _WEEK;
}
}
@openzeppelin/contracts/utils/structs/EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4;
import './pool/IAlgebraPoolImmutables.sol';
import './pool/IAlgebraPoolState.sol';
import './pool/IAlgebraPoolActions.sol';
import './pool/IAlgebraPoolPermissionedActions.sol';
import './pool/IAlgebraPoolEvents.sol';
import './pool/IAlgebraPoolErrors.sol';
/// @title The interface for a Algebra Pool
/// @dev The pool interface is broken up into many smaller pieces.
/// This interface includes custom error definitions and cannot be used in older versions of Solidity.
/// For older versions of Solidity use #IAlgebraPoolLegacy
/// Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPool is
IAlgebraPoolImmutables,
IAlgebraPoolState,
IAlgebraPoolActions,
IAlgebraPoolPermissionedActions,
IAlgebraPoolEvents,
IAlgebraPoolErrors
{
// used only for combining interfaces
}
@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolPermissionedActions.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by permissioned addresses
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolPermissionedActions {
/// @notice Set the community's % share of the fees. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
/// @param newCommunityFee The new community fee percent in thousandths (1e-3)
function setCommunityFee(uint16 newCommunityFee) external;
/// @notice Set the new tick spacing values. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
/// @param newTickSpacing The new tick spacing value
function setTickSpacing(int24 newTickSpacing) external;
/// @notice Set the new plugin address. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
/// @param newPluginAddress The new plugin address
function setPlugin(address newPluginAddress) external;
/// @notice Set new plugin config. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
/// @param newConfig In the new configuration of the plugin,
/// each bit of which is responsible for a particular hook.
function setPluginConfig(uint8 newConfig) external;
/// @notice Set new community fee vault address. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
/// @dev Community fee vault receives collected community fees.
/// **accumulated but not yet sent to the vault community fees once will be sent to the `newCommunityVault` address**
/// @param newCommunityVault The address of new community fee vault
function setCommunityVault(address newCommunityVault) external;
/// @notice Set new pool fee. Can be called by owner if dynamic fee is disabled.
/// Called by the plugin if dynamic fee is enabled
/// @param newFee The new fee value
function setFee(uint16 newFee) external;
}
@cryptoalgebra/integral-periphery/contracts/libraries/PoolAddress.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Provides functions for deriving a pool address from the poolDeployer and tokens
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-periphery
library PoolAddress {
bytes32 internal constant POOL_INIT_CODE_HASH = 0xe4894f29e2491e531db85584561de8b8869774d41313c860cf4089d80a51d8a4;
/// @notice The identifying key of the pool
struct PoolKey {
address token0;
address token1;
}
/// @notice Returns PoolKey: the ordered tokens
/// @param tokenA The first token of a pool, unsorted
/// @param tokenB The second token of a pool, unsorted
/// @return Poolkey The pool details with ordered token0 and token1 assignments
function getPoolKey(address tokenA, address tokenB) internal pure returns (PoolKey memory) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
return PoolKey({token0: tokenA, token1: tokenB});
}
/// @notice Deterministically computes the pool address given the poolDeployer and PoolKey
/// @param poolDeployer The Algebra poolDeployer contract address
/// @param key The PoolKey
/// @return pool The contract address of the Algebra pool
function computeAddress(address poolDeployer, PoolKey memory key) internal pure returns (address pool) {
require(key.token0 < key.token1, 'Invalid order of tokens');
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
poolDeployer,
keccak256(abi.encode(key.token0, key.token1)),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
}
contracts/lute/interfaces/ICompoundVeLUTEManagedNFTStrategyFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title Interface for the Compound VeLUTE Managed NFT Strategy Factory
* @notice This interface outlines the functions and events for a factory responsible for creating and managing strategies and virtual rewarders for Compound VeLUTE-managed NFTs.
*/
interface ICompoundVeLUTEManagedNFTStrategyFactory {
/**
* @dev Emitted when the address of the Router V2 Path Provider is updated.
*
* @param oldRouterV2PathProvider The address of the previous Router V2 Path Provider.
* @param newRouterV2PathProvider The address of the new Router V2 Path Provider that has been set.
*/
event SetRouterV2PathProvider(address indexed oldRouterV2PathProvider, address indexed newRouterV2PathProvider);
/**
* @dev Emitted when the implementation address for the virtual rewarder is changed.
*
* @param oldImplementation The previous implementation address of the virtual rewarder.
* @param newImplementation The new implementation address of the virtual rewarder that has been set.
*/
event ChangeVirtualRewarderImplementation(address indexed oldImplementation, address indexed newImplementation);
/**
* @dev Emitted when the implementation address for the strategy is changed.
*
* @param oldImplementation The previous implementation address of the strategy.
* @param newImplementation The new implementation address of the strategy that has been set.
*/
event ChangeStrategyImplementation(address indexed oldImplementation, address indexed newImplementation);
/**
* @dev Emitted when a new strategy and its corresponding virtual rewarder are created.
*
* @param strategy The address of the newly created strategy.
* @param virtualRewarder The address of the corresponding virtual rewarder created alongside the strategy.
* @param name The name assigned to the new strategy.
*/
event CreateStrategy(address indexed strategy, address indexed virtualRewarder, string name);
/**
* @notice Returns the current implementation address of the virtual rewarder.
* @return The address of the virtual rewarder implementation.
*/
function virtualRewarderImplementation() external view returns (address);
/**
* @notice Returns the current implementation address of the strategy.
* @return The address of the strategy implementation.
*/
function strategyImplementation() external view returns (address);
/**
* @notice Returns the address of the managed NFT manager associated with the strategies.
* @return The address of the managed NFT manager.
*/
function managedNFTManager() external view returns (address);
/**
* @notice Returns the address of the Router V2 Path Provider used to fetch and calculate
* optimal routes for token transactions within strategies.
* @return The address of the RouterV2PathProvider.
*/
function routerV2PathProvider() external view returns (address);
/**
* @notice Creates a new strategy with a specific name.
* @param name_ The name to assign to the new strategy.
* @return The address of the newly created strategy instance
*/
function createStrategy(string calldata name_) external returns (address);
/**
* @notice Sets a new RouterV2PathProvider.
* @param routerV2PathProvider_ The new address to set
*/
function setRouterV2PathProvider(address routerV2PathProvider_) external;
}
contracts/gauges/interfaces/IGaugeFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IGaugeFactory {
event GaugeImplementationChanged(address _oldGaugeImplementation, address _newGaugeImplementation);
function createGauge(
address _rewardToken,
address _ve,
address _token,
address _distribution,
address _internal_bribe,
address _external_bribe,
bool _isDistributeEmissionToMerkle,
address _feeVault
) external returns (address);
function gaugeImplementation() external view returns (address impl);
function merklGaugeMiddleman() external view returns (address);
function gaugeOwner() external view returns (address);
}
contracts/lute/interfaces/ISingelTokenBuyback.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IRouterV2} from "../../dexV2/interfaces/IRouterV2.sol";
interface ISingelTokenBuyback {
event BuybackTokenByV2(
address indexed caller,
address indexed inputToken,
address indexed outputToken,
IRouterV2.route[] routes,
uint256 inputAmount,
uint256 outputAmount
);
/**
* @notice Address of the Router V2 Path Provider used for fetching and calculating optimal token swap routes.
* @dev This address is utilized to access routing functionality for executing token buybacks.
*/
function routerV2PathProvider() external view returns (address);
/**
* @notice Buys back tokens by swapping specified input tokens for a target token via a DEX
* @dev Executes a token swap using the optimal route found via Router V2 Path Provider. Ensures input token is not the target token and validates slippage.
*
* @param inputToken_ The ERC20 token to swap from.
* @param inputRouters_ Array of routes to potentially use for the swap.
* @param slippage_ The maximum allowed slippage for the swap, in basis points.
* @param deadline_ Unix timestamp after which the transaction will revert.
*/
function buybackTokenByV2(
address inputToken_,
IRouterV2.route[] calldata inputRouters_,
uint256 slippage_,
uint256 deadline_
) external returns (uint256 outputAmount);
/**
* @notice Retrieves the target token for buybacks.
* @dev Provides an abstraction layer over internal details, potentially allowing for dynamic updates in the future.
* @return The address of the token targeted for buyback operations.
*/
function getBuybackTargetToken() external view returns (address);
}
@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721MetadataUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC721Upgradeable.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721MetadataUpgradeable is IERC721Upgradeable {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @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 amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` 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 amount) external returns (bool);
}
contracts/gauges/PerpetualsGaugeUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IRewardReciever} from "./interfaces/IRewardReciever.sol";
import {IPerpetualsGauge} from "./interfaces/IPerpetualsGauge.sol";
/**
* @title PerpetualsGaugeUpgradeable
* @dev This contract manages reward distribution in a gauge system for perpetual traders.
*/
contract PerpetualsGaugeUpgradeable is IPerpetualsGauge, OwnableUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @notice The address of the reward token
address public override rewardToken;
/// @notice The address of the reward receiver contract
address public override rewarder;
/// @notice The address authorized to distribute rewards
address public override DISTRIBUTION;
/// @notice The name of the gauge
string public override NAME;
/**
* @dev Error thrown when the provided reward token address is incorrect.
*/
error IncorrectRewardToken();
/**
* @dev Error thrown when an unauthorized address attempts to access restricted functionality.
*/
error AccessDenied();
error AddressZero();
/**
* @dev Modifier to check if the caller is the authorized voter.
*/
modifier onlyVoter() {
if (_msgSender() != DISTRIBUTION) {
revert AccessDenied();
}
_;
}
/**
* @dev Initializes the contract and disables initializers.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract with the given parameters.
* @param rewardToken_ The address of the reward token.
* @param voter_ The address authorized to distribute rewards.
* @param rewarder_ The address of the reward receiver contract.
* @param name_ The name of the gauge.
*/
function initialize(address rewardToken_, address voter_, address rewarder_, string memory name_) external initializer {
_checkAddressZero(rewardToken_);
_checkAddressZero(voter_);
_checkAddressZero(rewarder_);
__ReentrancyGuard_init();
__Ownable_init();
rewardToken = rewardToken_;
rewarder = rewarder_;
DISTRIBUTION = voter_;
NAME = name_;
}
/**
* @notice Notifies the contract of the reward amount to be distributed.
* @param token_ The address of the reward token.
* @param rewardAmount_ The amount of reward tokens to be distributed.
*/
function notifyRewardAmount(address token_, uint256 rewardAmount_) external override nonReentrant onlyVoter {
if (token_ != rewardToken) {
revert IncorrectRewardToken();
}
IERC20Upgradeable token = IERC20Upgradeable(token_);
IERC20Upgradeable(token).safeTransferFrom(DISTRIBUTION, address(this), rewardAmount_);
IRewardReciever rewarderCache = IRewardReciever(rewarder);
IERC20Upgradeable(token).forceApprove(address(rewarderCache), rewardAmount_);
rewarderCache.notifyRewardAmount(token_, rewardAmount_);
emit RewardAdded(rewardAmount_);
}
/**
* @notice Claims the fees for the internal_bribe.
* @return claimed0 The amount of the first token claimed.
* @return claimed1 The amount of the second token claimed.
*/
function claimFees() external override returns (uint256 claimed0, uint256 claimed1) {
return (0, 0);
}
/**
* @notice Gets the reward for a specific account.
* @param user_ The address of the account to get the reward for.
*/
function getReward(address user_) external override {}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/core/interfaces/IVoter.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IAccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol";
interface IVoter is IAccessControlUpgradeable {
/**
* @notice Represents the state of a gauge.
* @param isGauge Indicates if the address is a gauge.
* @param isAlive Indicates if the gauge is active.
* @param internalBribe The address of the internal bribe contract.
* @param externalBribe The address of the external bribe contract.
* @param pool The address of the associated pool.
* @param claimable The amount of rewards claimable by the gauge.
* @param index The current index used for reward distribution calculations.
* @param lastDistributionTimestamp The last time rewards were distributed.
*/
struct GaugeState {
bool isGauge;
bool isAlive;
address internalBribe;
address externalBribe;
address pool;
uint256 claimable;
uint256 index;
uint256 lastDistributionTimestamp;
}
/**
* @notice Parameters for creating a veNFT through VotingEscrow.
* @param percentageToLock The percentage (in 18 decimals) of the claimed reward tokens to be locked.
* @param lockDuration The duration (in seconds) for which the tokens will be locked.
* @param to The address that will receive the veNFT.
* @param shouldBoosted Indicates whether the veNFT should have boosted properties.
* @param withPermanentLock Indicates if the lock should be permanent.
* @param managedTokenIdForAttach The ID of the managed veNFT token to which this lock will be attached.
*/
struct AggregateCreateLockParams {
uint256 percentageToLock;
uint256 lockDuration;
address to;
bool shouldBoosted;
bool withPermanentLock;
uint256 managedTokenIdForAttach;
}
/**
* @notice Parameters for claiming bribes using a specific tokenId.
* @param tokenId The token ID to claim bribes for.
* @param bribes The array of bribe contract addresses.
* @param tokens The array of arrays containing token addresses for each bribe.
*/
struct AggregateClaimBribesByTokenIdParams {
uint256 tokenId;
address[] bribes;
address[][] tokens;
}
/**
* @notice Parameters for claiming bribes.
* @param bribes The array of bribe contract addresses.
* @param tokens The array of arrays containing token addresses for each bribe.
*/
struct AggregateClaimBribesParams {
address[] bribes;
address[][] tokens;
}
/**
* @notice Parameters for claiming Blaze data.
* @param totalAmount The total amount of reward being claimed.
* @param deadline The expiration time of the claim.
* @param signature The signature authorizing the claim.
*/
struct AggregateClaimBlazeDataParams {
uint256 totalAmount;
uint256 deadline;
bytes signature;
}
/**
* @notice Parameters for claiming VeLute Merkl airdrop data.
* @param inPureTokens_ Boolean indicating if the claim is in pure tokens.
* @param amount The amount to claim.
* @param withPermanentLock_ Whether the lock should be permanent.
* @param managedTokenIdForAttach_ The ID of the managed NFT to attach, if any. 0 for ignore
* @param proofs The array of Merkle proofs.
*/
struct AggregateClaimVeLuteMerklAirdrop {
bool inPureTokens;
uint256 amount;
bool withPermanentLock;
uint256 managedTokenIdForAttach;
bytes32[] proofs;
}
/**
* @notice Emitted when a gauge is created.
* @param gauge The address of the created gauge.
* @param creator The address of the creator.
* @param internalBribe The address of the created internal bribe.
* @param externalBribe The address of the created external bribe.
* @param pool The address of the associated pool.
*/
event GaugeCreated(address indexed gauge, address creator, address internalBribe, address indexed externalBribe, address indexed pool);
/**
* @notice Emitted when a gauge is created.
* @param gauge The address of the created gauge.
* @param gaugeType Type identifier of the created gauge.
*/
event GaugeCreatedType(address indexed gauge, uint256 indexed gaugeType);
/**
* @notice Emitted when a gauge is killed.
* @param gauge The address of the killed gauge.
*/
event GaugeKilled(address indexed gauge);
/**
* @notice Emitted when a gauge is revived.
* @param gauge The address of the revived gauge.
*/
event GaugeRevived(address indexed gauge);
/**
* @dev Emitted when a user casts votes for multiple pools using a specific token.
*
* @param voter The address of the user who cast the votes.
* @param tokenId The ID of the token used for voting.
* @param epoch The epoch during which the votes were cast.
* @param pools An array of pool addresses that received votes.
* @param voteWeights An array representing the weight of votes allocated to each pool.
*
* Requirements:
* - `pools` and `voteWeights` arrays must have the same length.
*
* Note: The voting power represented in `voteWeights` is allocated across the specified `votedPools` for the given `epoch`.
* The `totalVotingPower` represents the cumulative voting power used in this vote.
*/
event VoteCast(
address indexed voter,
uint256 indexed tokenId,
uint256 indexed epoch,
address[] pools,
uint256[] voteWeights,
uint256 totalVotingPower
);
/**
* @dev Emitted when a user resets all votes for the current epoch.
*
* @param voter The address of the user who resets their votes.
* @param tokenId The ID of the token used for voting that is being reset.
* @param epoch The epoch during which the votes were reset.
* @param totalResetVotingPower The total voting power that was reset.
*
* Note: This event indicates that all previously cast votes for the specified `epoch` have been reset for the given `votingTokenId`.
* The `totalResetVotingPower` represents the cumulative voting power that was removed during the reset.
*/
event VoteReset(address indexed voter, uint256 indexed tokenId, uint256 indexed epoch, uint256 totalResetVotingPower);
/**
* @notice Emitted when rewards are notified for distribution.
* @param sender The address of the sender.
* @param reward The address of the reward token.
* @param amount The amount of rewards to distribute.
*/
event NotifyReward(address indexed sender, address indexed reward, uint256 amount);
/**
* @notice Emitted when rewards are distributed to a gauge.
* @param sender The address of the sender.
* @param gauge The address of the gauge receiving the rewards.
* @param amount The amount of rewards distributed.
*/
event DistributeReward(address indexed sender, address indexed gauge, uint256 amount);
/**
* @notice Emitted when the vote delay is updated.
* @param old The previous vote delay.
* @param latest The new vote delay.
*/
event SetVoteDelay(uint256 old, uint256 latest);
/**
* @notice Emitted when a contract address is updated.
* @param key The key representing the contract.
* @param value The new address of the contract.
*/
event UpdateAddress(string key, address indexed value);
/// @notice Event emitted when voting is paused or unpaused.
/// @dev Emits the current paused state of voting.
/// @param paused Indicates whether voting is paused (true) or unpaused (false).
event VotingPaused(bool indexed paused);
/**
* @notice Emitted when the distribution window duration is set or updated.
* @param duration New duration of the distribution window in seconds.
*/
event SetDistributionWindowDuration(uint256 indexed duration);
/**
* @notice Emitted when a token is attached to a managed NFT.
* @param tokenId ID of the user's token that is being attached.
* @param managedTokenId ID of the managed token to which the user's token is attached.
*/
event AttachToManagedNFT(uint256 indexed tokenId, uint256 indexed managedTokenId);
/**
* @notice Emitted when a token is detached from a managed NFT.
* @param tokenId ID of the user's token that is being detached.
*/
event DettachFromManagedNFT(uint256 indexed tokenId);
/**
* @notice Updates the address of a specified contract.
* @param key_ The key representing the contract.
* @param value_ The new address of the contract.
*/
function updateAddress(string memory key_, address value_) external;
/**
* @notice Sets the duration of the distribution window for voting.
* @param distributionWindowDuration_ The duration in seconds.
*/
function setDistributionWindowDuration(uint256 distributionWindowDuration_) external;
/**
* @notice Disables a gauge, preventing further rewards distribution.
* @param gauge_ The address of the gauge to be disabled.
*/
function killGauge(address gauge_) external;
/**
* @notice Revives a previously disabled gauge, allowing it to distribute rewards again.
* @param gauge_ The address of the gauge to be revived.
*/
function reviveGauge(address gauge_) external;
/**
* @notice Creates a new V2 gauge for a specified pool.
* @param pool_ The address of the pool for which to create a gauge.
* @return gauge The address of the created gauge.
* @return internalBribe The address of the created internal bribe.
* @return externalBribe The address of the created external bribe.
*/
function createV2Gauge(address pool_) external returns (address gauge, address internalBribe, address externalBribe);
/**
* @notice Creates a new V3 gauge for a specified pool.
* @param pool_ The address of the pool for which to create a gauge.
* @return gauge The address of the created gauge.
* @return internalBribe The address of the created internal bribe.
* @return externalBribe The address of the created external bribe.
*/
function createV3Gauge(address pool_) external returns (address gauge, address internalBribe, address externalBribe);
/**
* @notice Creates a custom gauge with specified parameters.
* @param gauge_ The address of the custom gauge.
* @param pool_ The address of the pool for which to create a gauge.
* @param tokenA_ The address of token A in the pool.
* @param tokenB_ The address of token B in the pool.
* @param externalBribesName_ The name of the external bribe.
* @param internalBribesName_ The name of the internal bribe.
* @return gauge The address of the created gauge.
* @return internalBribe The address of the created internal bribe.
* @return externalBribe The address of the created external bribe.
*/
function createCustomGauge(
address gauge_,
address pool_,
address tokenA_,
address tokenB_,
string memory externalBribesName_,
string memory internalBribesName_
) external returns (address gauge, address internalBribe, address externalBribe);
/**
* @notice Notifies the contract of a reward amount to be distributed.
* @param amount_ The amount of rewards to distribute.
*/
function notifyRewardAmount(uint256 amount_) external;
/**
* @notice Distributes fees to a list of gauges.
* @param gauges_ An array of gauge addresses to distribute fees to.
*/
function distributeFees(address[] calldata gauges_) external;
/**
* @notice Distributes rewards to all pools managed by the contract.
*/
function distributeAll() external;
/**
* @notice Distributes rewards to a specified range of pools.
* @param start_ The starting index of the pool array.
* @param finish_ The ending index of the pool array.
*/
function distribute(uint256 start_, uint256 finish_) external;
/**
* @notice Distributes rewards to a specified list of gauges.
* @param gauges_ An array of gauge addresses to distribute rewards to.
*/
function distribute(address[] calldata gauges_) external;
/**
* @notice Resets the votes for a given NFT token ID.
* @param tokenId_ The token ID for which to reset votes.
*/
function reset(uint256 tokenId_) external;
/**
* @notice Updates the voting preferences for a given token ID.
* @param tokenId_ The token ID for which to update voting preferences.
*/
function poke(uint256 tokenId_) external;
/**
* @notice Casts votes for a given NFT token ID.
* @param tokenId_ The token ID for which to cast votes.
* @param poolsVotes_ An array of pool addresses to vote for.
* @param weights_ An array of weights corresponding to the pools.
*/
function vote(uint256 tokenId_, address[] calldata poolsVotes_, uint256[] calldata weights_) external;
/**
* @notice Claims rewards from multiple gauges.
* @param _gauges An array of gauge addresses to claim rewards from.
*/
function claimRewards(address[] memory _gauges) external;
/**
* @notice Claims bribes for a given NFT token ID from multiple bribe contracts.
* @param _bribes An array of bribe contract addresses to claim bribes from.
* @param _tokens An array of token arrays, specifying the tokens to claim.
* @param tokenId_ The token ID for which to claim bribes.
*/
function claimBribes(address[] memory _bribes, address[][] memory _tokens, uint256 tokenId_) external;
/**
* @notice Claims bribes from multiple bribe contracts.
* @param _bribes An array of bribe contract addresses to claim bribes from.
* @param _tokens An array of token arrays, specifying the tokens to claim.
*/
function claimBribes(address[] memory _bribes, address[][] memory _tokens) external;
/**
* @notice Handles the deposit of voting power to a managed NFT.
* @dev This function is called after tokens are deposited into the Voting Escrow contract for a managed NFT.
* Only callable by the Voting Escrow contract.
* @param tokenId_ The ID of the token that has received the deposit.
* @param managedTokenId_ The ID of the managed token receiving the voting power.
* @custom:error AccessDenied Thrown if the caller is not the Voting Escrow contract.
*/
function onDepositToManagedNFT(uint256 tokenId_, uint256 managedTokenId_) external;
/**
* @notice Attaches a tokenId to a managed tokenId.
* @param tokenId_ The user's tokenId to be attached.
* @param managedTokenId_ The managed tokenId to attach to.
*/
function attachToManagedNFT(uint256 tokenId_, uint256 managedTokenId_) external;
/**
* @notice Detaches a tokenId from its managed tokenId.
* @param tokenId_ The user's tokenId to be detached.
*/
function dettachFromManagedNFT(uint256 tokenId_) external;
/**
* @notice Checks if the provided address is a registered gauge.
* @param gauge_ The address of the gauge to check.
* @return True if the address is a registered gauge, false otherwise.
*/
function isGauge(address gauge_) external view returns (bool);
/**
* @notice Returns the state of a specific gauge.
* @param gauge_ The address of the gauge.
* @return GaugeState The current state of the specified gauge.
*/
function getGaugeState(address gauge_) external view returns (GaugeState memory);
/**
* @notice Checks if the specified gauge is alive (i.e., enabled for reward distribution).
* @param gauge_ The address of the gauge to check.
* @return True if the gauge is alive, false otherwise.
*/
function isAlive(address gauge_) external view returns (bool);
/**
* @notice Returns the pool address associated with a specified gauge.
* @param gauge_ The address of the gauge to query.
* @return The address of the pool associated with the specified gauge.
*/
function poolForGauge(address gauge_) external view returns (address);
/**
* @notice Returns the gauge address associated with a specified pool.
* @param pool_ The address of the pool to query.
* @return The address of the gauge associated with the specified pool.
*/
function poolToGauge(address pool_) external view returns (address);
/**
* @notice Returns the address of the Voting Escrow contract.
* @return The address of the Voting Escrow contract.
*/
function votingEscrow() external view returns (address);
/**
* @notice Returns the address of the Minter contract.
* @return The address of the Minter contract.
*/
function minter() external view returns (address);
/**
* @notice Returns the address of the V2 Pool Factory contract.
* @return The address of the V2 Pool Factory contract.
*/
function v2PoolFactory() external view returns (address);
/**
* @notice Returns the address of the V3 Pool Factory contract.
* @return The address of the V3 Pool Factory contract.
*/
function v3PoolFactory() external view returns (address);
/**
* @notice Returns the V2 pool address at a specific index.
* @param index The index of the V2 pool.
* @return The address of the V2 pool at the specified index.
*/
function v2Pools(uint256 index) external view returns (address);
/**
* @notice Returns the V3 pool address at a specific index.
* @param index The index of the V3 pool.
* @return The address of the V3 pool at the specified index.
*/
function v3Pools(uint256 index) external view returns (address);
/**
* @notice Returns the total number of pools, V2 pools, and V3 pools managed by the contract.
* @return totalCount The total number of pools.
* @return v2PoolsCount The total number of V2 pools.
* @return v3PoolsCount The total number of V3 pools.
*/
function poolsCounts() external view returns (uint256 totalCount, uint256 v2PoolsCount, uint256 v3PoolsCount);
/**
* @notice Returns the current epoch timestamp used for reward calculations.
* @return The current epoch timestamp.
*/
function epochTimestamp() external view returns (uint256);
/**
* @notice Returns the weight for a specific pool in a given epoch.
* @param timestamp The timestamp of the epoch.
* @param pool The address of the pool.
* @return The weight of the pool in the specified epoch.
*/
function weightsPerEpoch(uint256 timestamp, address pool) external view returns (uint256);
/**
* @notice Returns the vote weight of a specific NFT token ID for a given pool.
* @param tokenId The ID of the NFT.
* @param pool The address of the pool.
* @return The vote weight of the token for the specified pool.
*/
function votes(uint256 tokenId, address pool) external view returns (uint256);
/**
* @notice Returns the number of pools that an NFT token ID has voted for.
* @param tokenId The ID of the NFT.
* @return The number of pools the token has voted for.
*/
function poolVoteLength(uint256 tokenId) external view returns (uint256);
/**
* @notice Returns the pool address at a specific index for which the NFT token ID has voted.
* @param tokenId The ID of the NFT.
* @param index The index of the pool.
* @return The address of the pool at the specified index.
*/
function poolVote(uint256 tokenId, uint256 index) external view returns (address);
/**
* @notice Returns the last timestamp when an NFT token ID voted.
* @param tokenId The ID of the NFT.
* @return The timestamp of the last vote.
*/
function lastVotedTimestamps(uint256 tokenId) external view returns (uint256);
/**
* @notice Called after a token transfer to update external logic or linkage.
* @dev Typically invoked by the VotingEscrow contract whenever a veNFT changes ownership.
* Implementations can handle scenario-specific logic such as emission extension or target lock updates.
* @param from_ The address from which the token is transferred.
* @param to_ The address to which the token is transferred.
* @param tokenId_ The ID of the token being transferred.
*/
function onAfterTokenTransfer(address from_, address to_, uint256 tokenId_) external;
/**
* @notice Called after two veNFT tokens are merged into one.
* @dev Typically invoked by the VotingEscrow contract during the merge operation.
* Implementations can adjust bookkeeping, reward balances, or other logic related to the merged tokens.
* @param fromTokenId_ The ID of the token that is merged (source).
* @param toTokenId_ The ID of the token that remains (destination).
*/
function onAfterTokenMerge(uint256 fromTokenId_, uint256 toTokenId_) external;
/**
* @notice This function is called by the CompoundEmissionExtension to process a user’s reward claims
* and determine how much of the claimed tokens will be routed into veNFT locks and/or bribe pools.
*
* @param target_ The address of the user for whom the emission claim is being processed.
* @param gauges_ The array of gauge addresses from which to claim rewards on behalf of `target_`.
* @param blaze_ Optional Blaze-based claim data (if the Voter supports Blaze claims).
*
* @return toTargetLocks The portion of claimed tokens that should go into veNFT locks.
* @return toTargetBribePools The portion of claimed tokens that should go into bribe pools.
*/
function onCompoundEmissionClaim(
address target_,
address[] calldata gauges_,
AggregateClaimBlazeDataParams calldata blaze_
) external returns (uint256 toTargetLocks, uint256 toTargetBribePools);
}
contracts/lute/interfaces/IManagedNFTManager.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title Interface for Managed NFT Manager
* @dev Defines the functions and events for managing NFTs, including attaching/detaching to strategies, authorization, and administrative checks.
*/
interface IManagedNFTManager {
/**
* @dev Emitted when the disabled state of a managed NFT is toggled.
* @param sender The address that triggered the state change.
* @param tokenId The ID of the managed NFT affected.
* @param isDisable True if the NFT is now disabled, false if it is enabled.
*/
event ToggleDisableManagedNFT(address indexed sender, uint256 indexed tokenId, bool indexed isDisable);
/**
* @dev Emitted when a new managed NFT is created and attached to a strategy.
* @param sender The address that performed the creation.
* @param strategy The address of the strategy to which the NFT is attached.
* @param tokenId The ID of the newly created managed NFT.
*/
event CreateManagedNFT(address indexed sender, address indexed strategy, uint256 indexed tokenId);
/**
* @dev Emitted when an NFT is whitelisted or removed from the whitelist.
* @param tokenId The ID of the NFT being modified.
* @param isWhitelisted True if the NFT is being whitelisted, false if it is being removed from the whitelist.
*/
event SetWhitelistedNFT(uint256 indexed tokenId, bool indexed isWhitelisted);
/**
* @dev Emitted when an authorized user is set for a managed NFT.
* @param managedTokenId The ID of the managed NFT.
* @param authorizedUser The address being authorized.
*/
event SetAuthorizedUser(uint256 indexed managedTokenId, address authorizedUser);
/**
* @dev Emitted when the strategy flags for a specific strategy are updated.
* @param strategy The address of the strategy whose flags are being updated.
* @param flags The new set of flags assigned to the strategy.
*/
event SetStrategyFlags(address indexed strategy, uint8 flags);
/**
* @notice Emitted when the default detachment-lock duration is updated.
* @param previousDuration The previous default duration (in seconds).
* @param newDuration The new default duration (in seconds).
*/
event SetDefaultDetachmentLockDuration(
uint256 previousDuration,
uint256 newDuration
);
/**
* @notice Checks if a managed NFT is currently disabled.
* @param managedTokenId_ The ID of the managed NFT.
* @return True if the managed NFT is disabled, false otherwise.
*/
function isDisabledNFT(uint256 managedTokenId_) external view returns (bool);
/**
* @notice Determines if a token ID is recognized as a managed NFT within the system.
* @param managedTokenId_ The ID of the token to check.
* @return True if the token is a managed NFT, false otherwise.
*/
function isManagedNFT(uint256 managedTokenId_) external view returns (bool);
/**
* @notice Checks if an NFT is whitelisted within the management system.
* @param tokenId_ The ID of the NFT to check.
* @return True if the NFT is whitelisted, false otherwise.
*/
function isWhitelistedNFT(uint256 tokenId_) external view returns (bool);
/**
* @notice Verifies if a user's NFT is attached to any managed NFT.
* @param tokenId_ The ID of the user's NFT.
* @return True if the NFT is attached, false otherwise.
*/
function isAttachedNFT(uint256 tokenId_) external view returns (bool);
/**
* @notice Checks if a given account is an administrator of the managed NFT system.
* @param account_ The address to check.
* @return True if the address is an admin, false otherwise.
*/
function isAdmin(address account_) external view returns (bool);
/**
* @notice Retrieves the managed token ID that a user's NFT is attached to.
* @param tokenId_ The ID of the user's NFT.
* @return The ID of the managed token to which the NFT is attached.
*/
function getAttachedManagedTokenId(uint256 tokenId_) external view returns (uint256);
/**
* @notice Address of the Voting Escrow contract managing voting and staking mechanisms.
*/
function votingEscrow() external view returns (address);
/**
* @notice Address of the Voter contract responsible for handling governance actions related to managed NFTs.
*/
function voter() external view returns (address);
/**
* @notice Verifies if a given address is authorized to manage a specific managed NFT.
* @param managedTokenId_ The ID of the managed NFT.
* @param account_ The address to verify.
* @return True if the address is authorized, false otherwise.
*/
function isAuthorized(uint256 managedTokenId_, address account_) external view returns (bool);
/**
* @notice Retrieves the strategy flags for a given strategy.
* @param strategy_ The address of the strategy to retrieve flags for.
* @return The flags assigned to the specified strategy.
*/
function getStrategyFlags(address strategy_) external view returns (uint8);
/**
* @notice Returns the default detachment/withdrawal lock window duration in seconds.
* @dev Strategies should treat this as the baseline lock window after epoch start
* unless an explicit per-strategy override applies.
* @return duration The default lock duration, in seconds.
*/
function defaultDetachmentLockDuration() external view returns (uint256 duration);
/**
* @notice Assigns an authorized user for a managed NFT.
* @param managedTokenId_ The ID of the managed NFT.
* @param authorizedUser_ The address to authorize.
*/
function setAuthorizedUser(uint256 managedTokenId_, address authorizedUser_) external;
/**
* @notice Creates a managed NFT and attaches it to a strategy
* @param strategy_ The strategy to which the managed NFT will be attached
*/
function createManagedNFT(address strategy_) external returns (uint256 managedTokenId);
/**
* @notice Toggles the disabled state of a managed NFT
* @param managedTokenId_ The ID of the managed token to toggle
* @dev Enables or disables a managed token to control its operational status, with an event emitted for state change.
*/
function toggleDisableManagedNFT(uint256 managedTokenId_) external;
/**
* @notice Attaches a user's NFT to a managed NFT, enabling it within a specific strategy.
* @param tokenId_ The user's NFT token ID.
* @param managedTokenId The managed NFT token ID.
*/
function onAttachToManagedNFT(uint256 tokenId_, uint256 managedTokenId) external;
/**
* @notice Detaches a user's NFT from a managed NFT, disabling it within the strategy.
* @param tokenId_ The user's NFT token ID.
*/
function onDettachFromManagedNFT(uint256 tokenId_) external;
/**
* @notice Handles the deposit of tokens to an NFT attached to a managed token.
* @dev Called by the Voting Escrow contract when tokens are deposited to an NFT that is attached to a managed NFT.
* The function verifies the token is attached, checks if it is disabled, and updates the token's state.
* @param tokenId_ The token ID of the user's NFT.
* @param amount_ The amount of tokens to deposit.
* @custom:error IncorrectUserNFT Thrown if the provided token ID is not attached or if it is a managed token itself.
* @custom:error ManagedNFTIsDisabled Thrown if the managed token is currently disabled.
*/
function onDepositToAttachedNFT(uint256 tokenId_, uint256 amount_) external;
}
@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.8;
import "./ECDSAUpgradeable.sol";
import "../../interfaces/IERC5267Upgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
* thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
* they need in their contracts using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*
* _Available since v3.4._
*
* @custom:storage-size 52
*/
abstract contract EIP712Upgradeable is Initializable, IERC5267Upgradeable {
bytes32 private constant _TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @custom:oz-renamed-from _HASHED_NAME
bytes32 private _hashedName;
/// @custom:oz-renamed-from _HASHED_VERSION
bytes32 private _hashedVersion;
string private _name;
string private _version;
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
__EIP712_init_unchained(name, version);
}
function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
_name = name;
_version = version;
// Reset prior values in storage if upgrading
_hashedName = 0;
_hashedVersion = 0;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
return _buildDomainSeparator();
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/**
* @dev See {EIP-5267}.
*
* _Available since v4.9._
*/
function eip712Domain()
public
view
virtual
override
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
// If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
// and the EIP712 domain is not reliable, as it will be missing name and version.
require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized");
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Name() internal virtual view returns (string memory) {
return _name;
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Version() internal virtual view returns (string memory) {
return _version;
}
/**
* @dev The hash of the name parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
*/
function _EIP712NameHash() internal view returns (bytes32) {
string memory name = _EIP712Name();
if (bytes(name).length > 0) {
return keccak256(bytes(name));
} else {
// If the name is empty, the contract may have been upgraded without initializing the new storage.
// We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
bytes32 hashedName = _hashedName;
if (hashedName != 0) {
return hashedName;
} else {
return keccak256("");
}
}
}
/**
* @dev The hash of the version parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
*/
function _EIP712VersionHash() internal view returns (bytes32) {
string memory version = _EIP712Version();
if (bytes(version).length > 0) {
return keccak256(bytes(version));
} else {
// If the version is empty, the contract may have been upgraded without initializing the new storage.
// We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
bytes32 hashedVersion = _hashedVersion;
if (hashedVersion != 0) {
return hashedVersion;
} else {
return keccak256("");
}
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
}
contracts/dexV2/Pair.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {IERC20Metadata, IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IPair} from "./interfaces/IPair.sol";
import {IPairCallee} from "./interfaces/IPairCallee.sol";
import {IPairFactory} from "./interfaces/IPairFactory.sol";
import {PairFees} from "./PairFees.sol";
// The base pair of pools, either stable or volatile
contract Pair is IPair {
string public name;
string public symbol;
uint8 public constant decimals = 18;
// Used to denote stable or volatile pair, not immutable since construction happens in the initialize method for CREATE2 deterministic addresses
bool public stable;
uint public totalSupply = 0;
mapping(address => mapping(address => uint)) public allowance;
mapping(address => uint) public balanceOf;
bytes32 internal DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 internal constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public nonces;
uint internal constant MINIMUM_LIQUIDITY = 10 ** 3;
uint256 internal constant MINIMUM_K = 10 ** 10;
address public token0;
address public token1;
address public fees;
address public factory;
address public communityVault;
// Structure to capture time period obervations every 30 minutes, used for local oracles
struct Observation {
uint timestamp;
uint reserve0Cumulative;
uint reserve1Cumulative;
}
// Capture oracle reading every 30 minutes
uint constant periodSize = 1800;
Observation[] public observations;
uint internal decimals0;
uint internal decimals1;
uint public reserve0;
uint public reserve1;
uint public blockTimestampLast;
uint public reserve0CumulativeLast;
uint public reserve1CumulativeLast;
// index0 and index1 are used to accumulate fees, this is split out from normal trades to keep the swap "clean"
// this further allows LP holders to easily claim fees for tokens they have/staked
uint public index0 = 0;
uint public index1 = 0;
// position assigned to each LP to track their current index0 & index1 vs the global position
mapping(address => uint) public supplyIndex0;
mapping(address => uint) public supplyIndex1;
// tracks the amount of unclaimed, but claimable tokens off of fees for token0 and token1
mapping(address => uint) public claimable0;
mapping(address => uint) public claimable1;
event Fees(address indexed sender, uint amount0, uint amount1);
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to);
event Sync(uint reserve0, uint reserve1);
event Claim(address indexed sender, address indexed recipient, uint amount0, uint amount1);
event SetCommunityVault(address indexed communityVault_);
event Transfer(address indexed from, address indexed to, uint amount);
event Approval(address indexed owner, address indexed spender, uint amount);
// simple re-entrancy check
uint internal _unlocked;
modifier lock() {
require(_unlocked == 1);
_unlocked = 2;
_;
_unlocked = 1;
}
function initialize(address _token0, address _token1, bool _stable, address _communityVault) external {
require(factory == address(0), "Initialized");
factory = msg.sender;
(token0, token1, stable, communityVault) = (_token0, _token1, _stable, _communityVault);
fees = address(new PairFees(msg.sender, _token0, _token1));
_unlocked = 1;
if (_stable) {
name = string(abi.encodePacked("StableV1 AMM - ", IERC20Metadata(_token0).symbol(), "/", IERC20Metadata(_token1).symbol()));
symbol = string(abi.encodePacked("sAMM-", IERC20Metadata(_token0).symbol(), "/", IERC20Metadata(_token1).symbol()));
} else {
name = string(abi.encodePacked("VolatileV1 AMM - ", IERC20Metadata(_token0).symbol(), "/", IERC20Metadata(_token1).symbol()));
symbol = string(abi.encodePacked("vAMM-", IERC20Metadata(_token0).symbol(), "/", IERC20Metadata(_token1).symbol()));
}
decimals0 = 10 ** IERC20Metadata(_token0).decimals();
decimals1 = 10 ** IERC20Metadata(_token1).decimals();
observations.push(Observation(block.timestamp, 0, 0));
}
function setCommunityVault(address communityVault_) external virtual override {
IPairFactory factoryCache = IPairFactory(factory);
require(factoryCache.hasRole(factoryCache.PAIRS_ADMINISTRATOR_ROLE(), msg.sender), "ACCESS_DENIED");
communityVault = communityVault_;
emit SetCommunityVault(communityVault_);
}
function observationLength() external view returns (uint) {
return observations.length;
}
function lastObservation() public view returns (Observation memory) {
return observations[observations.length - 1];
}
function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) {
return (decimals0, decimals1, reserve0, reserve1, stable, token0, token1);
}
function tokens() external view returns (address, address) {
return (token0, token1);
}
function isStable() external view returns (bool) {
return stable;
}
// claim accumulated but unclaimed fees (viewable via claimable0 and claimable1)
function claimFees() external returns (uint claimed0, uint claimed1) {
_updateFor(msg.sender);
claimed0 = claimable0[msg.sender];
claimed1 = claimable1[msg.sender];
if (claimed0 > 0 || claimed1 > 0) {
claimable0[msg.sender] = 0;
claimable1[msg.sender] = 0;
PairFees(fees).claimFeesFor(msg.sender, claimed0, claimed1);
emit Claim(msg.sender, msg.sender, claimed0, claimed1);
}
}
// Accrue fees on token0
function _update0(uint amount) internal {
// get protocol fee
uint256 _protocolFee = 0;
address communityVaultCache = communityVault;
if (communityVaultCache != address(0)) {
uint256 _protocolFeeRate = IPairFactory(factory).getProtocolFee(address(this));
if (_protocolFeeRate > 0) {
_protocolFee = (amount * _protocolFeeRate) / 10000;
_safeTransfer(token0, communityVaultCache, _protocolFee);
amount -= _protocolFee;
}
}
if (amount > 0) {
_safeTransfer(token0, fees, amount);
uint256 _ratio = (amount * 1e18) / totalSupply; // 1e18 adjustment is removed during claim
if (_ratio > 0) {
index0 += _ratio;
}
}
emit Fees(msg.sender, amount + _protocolFee, 0);
}
// Accrue fees on token1
function _update1(uint amount) internal {
// get protocol fee
uint256 _protocolFee = 0;
address communityVaultCache = communityVault;
if (communityVaultCache != address(0)) {
uint256 _protocolFeeRate = IPairFactory(factory).getProtocolFee(address(this));
if (_protocolFeeRate > 0) {
_protocolFee = (amount * _protocolFeeRate) / 10000;
_safeTransfer(token1, communityVaultCache, _protocolFee); // transfer the fees out to PairFees
amount -= _protocolFee;
}
}
if (amount > 0) {
_safeTransfer(token1, fees, amount);
uint256 _ratio = (amount * 1e18) / totalSupply;
if (_ratio > 0) {
index1 += _ratio;
}
}
emit Fees(msg.sender, 0, amount + _protocolFee);
}
// this function MUST be called on any balance changes, otherwise can be used to infinitely claim fees
// Fees are segregated from core funds, so fees can never put liquidity at risk
function _updateFor(address recipient) internal {
uint _supplied = balanceOf[recipient]; // get LP balance of `recipient`
if (_supplied > 0) {
uint _supplyIndex0 = supplyIndex0[recipient]; // get last adjusted index0 for recipient
uint _supplyIndex1 = supplyIndex1[recipient];
uint _index0 = index0; // get global index0 for accumulated fees
uint _index1 = index1;
supplyIndex0[recipient] = _index0; // update user current position to global position
supplyIndex1[recipient] = _index1;
uint _delta0 = _index0 - _supplyIndex0; // see if there is any difference that need to be accrued
uint _delta1 = _index1 - _supplyIndex1;
if (_delta0 > 0) {
uint _share = (_supplied * _delta0) / 1e18; // add accrued difference for each supplied token
claimable0[recipient] += _share;
}
if (_delta1 > 0) {
uint _share = (_supplied * _delta1) / 1e18;
claimable1[recipient] += _share;
}
} else {
supplyIndex0[recipient] = index0; // new users are set to the default global state
supplyIndex1[recipient] = index1;
}
}
function getReserves() public view returns (uint _reserve0, uint _reserve1, uint _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
// update reserves and, on the first call per block, price accumulators
function _update(uint balance0, uint balance1, uint _reserve0, uint _reserve1) internal {
uint blockTimestamp = block.timestamp;
uint timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
reserve0CumulativeLast += _reserve0 * timeElapsed;
reserve1CumulativeLast += _reserve1 * timeElapsed;
}
Observation memory _point = lastObservation();
timeElapsed = blockTimestamp - _point.timestamp; // compare the last observation with current timestamp, if greater than 30 minutes, record a new event
if (timeElapsed > periodSize) {
observations.push(Observation(blockTimestamp, reserve0CumulativeLast, reserve1CumulativeLast));
}
reserve0 = balance0;
reserve1 = balance1;
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
// produces the cumulative price using counterfactuals to save gas and avoid a call to sync.
function currentCumulativePrices() public view returns (uint reserve0Cumulative, uint reserve1Cumulative, uint blockTimestamp) {
blockTimestamp = block.timestamp;
reserve0Cumulative = reserve0CumulativeLast;
reserve1Cumulative = reserve1CumulativeLast;
// if time has elapsed since the last update on the pair, mock the accumulated price values
(uint _reserve0, uint _reserve1, uint _blockTimestampLast) = getReserves();
if (_blockTimestampLast != blockTimestamp) {
// subtraction overflow is desired
uint timeElapsed = blockTimestamp - _blockTimestampLast;
reserve0Cumulative += _reserve0 * timeElapsed;
reserve1Cumulative += _reserve1 * timeElapsed;
}
}
// as per `current`, however allows user configured granularity, up to the full window size
function quote(address tokenIn, uint amountIn, uint granularity) external view returns (uint amountOut) {
uint[] memory _prices = sample(tokenIn, amountIn, granularity, 1);
uint priceAverageCumulative;
for (uint i = 0; i < _prices.length; i++) {
priceAverageCumulative += _prices[i];
}
return priceAverageCumulative / granularity;
}
// returns a memory set of twap prices
function prices(address tokenIn, uint amountIn, uint points) external view returns (uint[] memory) {
return sample(tokenIn, amountIn, points, 1);
}
function sample(address tokenIn, uint amountIn, uint points, uint window) public view returns (uint[] memory) {
uint[] memory _prices = new uint[](points);
uint length = observations.length - 1;
uint i = length - (points * window);
uint nextIndex = 0;
uint index = 0;
for (; i < length; i += window) {
nextIndex = i + window;
uint timeElapsed = observations[nextIndex].timestamp - observations[i].timestamp;
uint _reserve0 = (observations[nextIndex].reserve0Cumulative - observations[i].reserve0Cumulative) / timeElapsed;
uint _reserve1 = (observations[nextIndex].reserve1Cumulative - observations[i].reserve1Cumulative) / timeElapsed;
_prices[index] = _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1);
// index < length; length cannot overflow
unchecked {
index = index + 1;
}
}
return _prices;
}
// this low-level function should be called by addLiquidity functions in Router.sol, which performs important safety checks
// standard uniswap v2 implementation
function mint(address to) external lock returns (uint liquidity) {
(uint _reserve0, uint _reserve1) = (reserve0, reserve1);
uint _balance0 = IERC20(token0).balanceOf(address(this));
uint _balance1 = IERC20(token1).balanceOf(address(this));
uint _amount0 = _balance0 - _reserve0;
uint _amount1 = _balance1 - _reserve1;
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
if (_totalSupply == 0) {
liquidity = Math.sqrt(_amount0 * _amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
if (stable) {
require((_amount0 * 1e18) / decimals0 == (_amount1 * 1e18) / decimals1, "Pair: stable deposits must be equal");
require(_k(_amount0, _amount1) > MINIMUM_K, "Pair: stable deposits must be above minimum k");
}
} else {
liquidity = Math.min((_amount0 * _totalSupply) / _reserve0, (_amount1 * _totalSupply) / _reserve1);
}
require(liquidity > 0, "ILM"); // Pair: INSUFFICIENT_LIQUIDITY_MINTED
_mint(to, liquidity);
_update(_balance0, _balance1, _reserve0, _reserve1);
emit Mint(msg.sender, _amount0, _amount1);
}
// this low-level function should be called from a contract which performs important safety checks
// standard uniswap v2 implementation
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint _reserve0, uint _reserve1) = (reserve0, reserve1);
(address _token0, address _token1) = (token0, token1);
uint _balance0 = IERC20(_token0).balanceOf(address(this));
uint _balance1 = IERC20(_token1).balanceOf(address(this));
uint _liquidity = balanceOf[address(this)];
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
amount0 = (_liquidity * _balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = (_liquidity * _balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, "ILB"); // Pair: INSUFFICIENT_LIQUIDITY_BURNED
if (stable) {
uint256 _remainder0 = _balance0 - amount0;
uint256 _remainder1 = _balance1 - amount1;
require(_k(_remainder0, _remainder1) >= MINIMUM_K, "Pair: K must be greater than minimum k"); // Pair: K must be greater than minimum k
}
_burn(address(this), _liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
_balance0 = IERC20(_token0).balanceOf(address(this));
_balance1 = IERC20(_token1).balanceOf(address(this));
_update(_balance0, _balance1, _reserve0, _reserve1);
emit Burn(msg.sender, amount0, amount1, to);
}
// this low-level function should be called from a contract which performs important safety checks
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
IPairFactory factoryCache = IPairFactory(factory);
{
require(!factoryCache.isPaused());
address hookTarget = factoryCache.getHookTarget(address(this));
if (hookTarget != address(0)) {
IPairCallee(hookTarget).hook(msg.sender, amount0Out, amount1Out, data);
}
}
require(amount0Out > 0 || amount1Out > 0, "IOA"); // Pair: INSUFFICIENT_OUTPUT_AMOUNT
(uint _reserve0, uint _reserve1) = (reserve0, reserve1);
require(amount0Out < _reserve0 && amount1Out < _reserve1, "IL"); // Pair: INSUFFICIENT_LIQUIDITY
uint _balance0;
uint _balance1;
{
// scope for _token{0,1}, avoids stack too deep errors
(address _token0, address _token1) = (token0, token1);
require(to != _token0 && to != _token1, "IT"); // Pair: INVALID_TO
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IPairCallee(to).hook(msg.sender, amount0Out, amount1Out, data); // callback, used for flash loans
_balance0 = IERC20(_token0).balanceOf(address(this));
_balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = _balance0 > _reserve0 - amount0Out ? _balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = _balance1 > _reserve1 - amount1Out ? _balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, "IIA"); // Pair: INSUFFICIENT_INPUT_AMOUNT
{
// scope for reserve{0,1}Adjusted, avoids stack too deep errors
(address _token0, address _token1) = (token0, token1);
if (amount0In > 0) _update0((amount0In * factoryCache.getFee(address(this), stable)) / 10000); // accrue fees for token0 and move them out of pool
if (amount1In > 0) _update1((amount1In * factoryCache.getFee(address(this), stable)) / 10000); // accrue fees for token1 and move them out of pool
_balance0 = IERC20(_token0).balanceOf(address(this)); // since we removed tokens, we need to reconfirm balances, can also simply use previous balance - amountIn/ 10000, but doing balanceOf again as safety check
_balance1 = IERC20(_token1).balanceOf(address(this));
// The curve, either x3y+y3x for stable pools, or x*y for volatile pools
require(_k(_balance0, _balance1) >= _k(_reserve0, _reserve1), "K"); // Pair: K
}
_update(_balance0, _balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
// force balances to match reserves
function skim(address to) external lock {
(address _token0, address _token1) = (token0, token1);
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)) - (reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)) - (reserve1));
}
// force reserves to match balances
function sync() external lock {
require(totalSupply > 0, "Pair: zero total supply");
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
function _f(uint256 x0, uint256 y) internal pure returns (uint256) {
uint256 _a = (x0 * y) / 1e18;
uint256 _b = ((x0 * x0) / 1e18 + (y * y) / 1e18);
return (_a * _b) / 1e18;
}
function _d(uint x0, uint y) internal pure returns (uint) {
return (3 * x0 * ((y * y) / 1e18)) / 1e18 + ((((x0 * x0) / 1e18) * x0) / 1e18);
}
function _get_y(uint x0, uint xy, uint y) internal view returns (uint) {
for (uint256 i = 0; i < 255; i++) {
uint256 k = _f(x0, y);
if (k < xy) {
// there are two cases where dy == 0
// case 1: The y is converged and we find the correct answer
// case 2: _d(x0, y) is too large compare to (xy - k) and the rounding error
// screwed us.
// In this case, we need to increase y by 1
uint256 dy = ((xy - k) * 1e18) / _d(x0, y);
if (dy == 0) {
if (k == xy) {
// We found the correct answer. Return y
return y;
}
if (_f(x0, y + 1) > xy) {
// If _k(x0, y + 1) > xy, then we are close to the correct answer.
// There's no closer answer than y + 1
return y + 1;
}
dy = 1;
}
y = y + dy;
} else {
uint256 dy = ((k - xy) * 1e18) / _d(x0, y);
if (dy == 0) {
if (k == xy || _f(x0, y - 1) < xy) {
// Likewise, if k == xy, we found the correct answer.
// If _f(x0, y - 1) < xy, then we are close to the correct answer.
// There's no closer answer than "y"
// It's worth mentioning that we need to find y where f(x0, y) >= xy
// As a result, we can't return y - 1 even it's closer to the correct answer
return y;
}
dy = 1;
}
y = y - dy;
}
}
revert("!y");
}
function getAmountOut(uint amountIn, address tokenIn) external view returns (uint) {
(uint _reserve0, uint _reserve1) = (reserve0, reserve1);
amountIn -= (amountIn * IPairFactory(factory).getFee(address(this), stable)) / 10000; // remove fee from amount received
return _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1);
}
function _getAmountOut(uint amountIn, address tokenIn, uint _reserve0, uint _reserve1) internal view returns (uint) {
if (stable) {
uint xy = _k(_reserve0, _reserve1);
_reserve0 = (_reserve0 * 1e18) / decimals0;
_reserve1 = (_reserve1 * 1e18) / decimals1;
(uint reserveA, uint reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0);
amountIn = tokenIn == token0 ? (amountIn * 1e18) / decimals0 : (amountIn * 1e18) / decimals1;
uint y = reserveB - _get_y(amountIn + reserveA, xy, reserveB);
return (y * (tokenIn == token0 ? decimals1 : decimals0)) / 1e18;
} else {
(uint reserveA, uint reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0);
return (amountIn * reserveB) / (reserveA + amountIn);
}
}
function _k(uint x, uint y) internal view returns (uint) {
if (stable) {
uint _x = (x * 1e18) / decimals0;
uint _y = (y * 1e18) / decimals1;
uint _a = (_x * _y) / 1e18;
uint _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18);
return (_a * _b) / 1e18; // x3y+y3x >= k
} else {
return x * y; // xy >= k
}
}
function _mint(address dst, uint amount) internal {
_updateFor(dst); // balances must be updated on mint/burn/transfer
totalSupply += amount;
balanceOf[dst] += amount;
emit Transfer(address(0), dst, amount);
}
function _burn(address dst, uint amount) internal {
_updateFor(dst);
totalSupply -= amount;
balanceOf[dst] -= amount;
emit Transfer(dst, address(0), amount);
}
function approve(address spender, uint amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, "Pair: EXPIRED");
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, "Pair: INVALID_SIGNATURE");
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function transfer(address dst, uint amount) external returns (bool) {
_transferTokens(msg.sender, dst, amount);
return true;
}
function transferFrom(address src, address dst, uint amount) external returns (bool) {
address spender = msg.sender;
uint spenderAllowance = allowance[src][spender];
if (spender != src && spenderAllowance != type(uint).max) {
uint newAllowance = spenderAllowance - amount;
allowance[src][spender] = newAllowance;
emit Approval(src, spender, newAllowance);
}
_transferTokens(src, dst, amount);
return true;
}
function _transferTokens(address src, address dst, uint amount) internal {
_updateFor(src); // update fee position for src
_updateFor(dst); // update fee position for dst
balanceOf[src] -= amount;
balanceOf[dst] += amount;
emit Transfer(src, dst, amount);
}
function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
function _safeApprove(address token, address spender, uint256 value) internal {
require(token.code.length > 0);
require(
(value == 0) || (IERC20(token).allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
}
@openzeppelin/contracts/governance/TimelockController.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (governance/TimelockController.sol)
pragma solidity ^0.8.0;
import "../access/AccessControl.sol";
import "../token/ERC721/IERC721Receiver.sol";
import "../token/ERC1155/IERC1155Receiver.sol";
/**
* @dev Contract module which acts as a timelocked controller. When set as the
* owner of an `Ownable` smart contract, it enforces a timelock on all
* `onlyOwner` maintenance operations. This gives time for users of the
* controlled contract to exit before a potentially dangerous maintenance
* operation is applied.
*
* By default, this contract is self administered, meaning administration tasks
* have to go through the timelock process. The proposer (resp executor) role
* is in charge of proposing (resp executing) operations. A common use case is
* to position this {TimelockController} as the owner of a smart contract, with
* a multisig or a DAO as the sole proposer.
*
* _Available since v3.3._
*/
contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver {
bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE");
uint256 internal constant _DONE_TIMESTAMP = uint256(1);
mapping(bytes32 => uint256) private _timestamps;
uint256 private _minDelay;
/**
* @dev Emitted when a call is scheduled as part of operation `id`.
*/
event CallScheduled(
bytes32 indexed id,
uint256 indexed index,
address target,
uint256 value,
bytes data,
bytes32 predecessor,
uint256 delay
);
/**
* @dev Emitted when a call is performed as part of operation `id`.
*/
event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data);
/**
* @dev Emitted when new proposal is scheduled with non-zero salt.
*/
event CallSalt(bytes32 indexed id, bytes32 salt);
/**
* @dev Emitted when operation `id` is cancelled.
*/
event Cancelled(bytes32 indexed id);
/**
* @dev Emitted when the minimum delay for future operations is modified.
*/
event MinDelayChange(uint256 oldDuration, uint256 newDuration);
/**
* @dev Initializes the contract with the following parameters:
*
* - `minDelay`: initial minimum delay for operations
* - `proposers`: accounts to be granted proposer and canceller roles
* - `executors`: accounts to be granted executor role
* - `admin`: optional account to be granted admin role; disable with zero address
*
* IMPORTANT: The optional admin can aid with initial configuration of roles after deployment
* without being subject to delay, but this role should be subsequently renounced in favor of
* administration through timelocked proposals. Previous versions of this contract would assign
* this admin to the deployer automatically and should be renounced as well.
*/
constructor(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) {
_setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE);
// self administration
_setupRole(TIMELOCK_ADMIN_ROLE, address(this));
// optional admin
if (admin != address(0)) {
_setupRole(TIMELOCK_ADMIN_ROLE, admin);
}
// register proposers and cancellers
for (uint256 i = 0; i < proposers.length; ++i) {
_setupRole(PROPOSER_ROLE, proposers[i]);
_setupRole(CANCELLER_ROLE, proposers[i]);
}
// register executors
for (uint256 i = 0; i < executors.length; ++i) {
_setupRole(EXECUTOR_ROLE, executors[i]);
}
_minDelay = minDelay;
emit MinDelayChange(0, minDelay);
}
/**
* @dev Modifier to make a function callable only by a certain role. In
* addition to checking the sender's role, `address(0)` 's role is also
* considered. Granting a role to `address(0)` is equivalent to enabling
* this role for everyone.
*/
modifier onlyRoleOrOpenRole(bytes32 role) {
if (!hasRole(role, address(0))) {
_checkRole(role, _msgSender());
}
_;
}
/**
* @dev Contract might receive/hold ETH as part of the maintenance process.
*/
receive() external payable {}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, AccessControl) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns whether an id correspond to a registered operation. This
* includes both Pending, Ready and Done operations.
*/
function isOperation(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) > 0;
}
/**
* @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready".
*/
function isOperationPending(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) > _DONE_TIMESTAMP;
}
/**
* @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending".
*/
function isOperationReady(bytes32 id) public view virtual returns (bool) {
uint256 timestamp = getTimestamp(id);
return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
}
/**
* @dev Returns whether an operation is done or not.
*/
function isOperationDone(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) == _DONE_TIMESTAMP;
}
/**
* @dev Returns the timestamp at which an operation becomes ready (0 for
* unset operations, 1 for done operations).
*/
function getTimestamp(bytes32 id) public view virtual returns (uint256) {
return _timestamps[id];
}
/**
* @dev Returns the minimum delay for an operation to become valid.
*
* This value can be changed by executing an operation that calls `updateDelay`.
*/
function getMinDelay() public view virtual returns (uint256) {
return _minDelay;
}
/**
* @dev Returns the identifier of an operation containing a single
* transaction.
*/
function hashOperation(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return keccak256(abi.encode(target, value, data, predecessor, salt));
}
/**
* @dev Returns the identifier of an operation containing a batch of
* transactions.
*/
function hashOperationBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return keccak256(abi.encode(targets, values, payloads, predecessor, salt));
}
/**
* @dev Schedule an operation containing a single transaction.
*
* Emits {CallSalt} if salt is nonzero, and {CallScheduled}.
*
* Requirements:
*
* - the caller must have the 'proposer' role.
*/
function schedule(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
bytes32 id = hashOperation(target, value, data, predecessor, salt);
_schedule(id, delay);
emit CallScheduled(id, 0, target, value, data, predecessor, delay);
if (salt != bytes32(0)) {
emit CallSalt(id, salt);
}
}
/**
* @dev Schedule an operation containing a batch of transactions.
*
* Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch.
*
* Requirements:
*
* - the caller must have the 'proposer' role.
*/
function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == payloads.length, "TimelockController: length mismatch");
bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
_schedule(id, delay);
for (uint256 i = 0; i < targets.length; ++i) {
emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay);
}
if (salt != bytes32(0)) {
emit CallSalt(id, salt);
}
}
/**
* @dev Schedule an operation that is to become valid after a given delay.
*/
function _schedule(bytes32 id, uint256 delay) private {
require(!isOperation(id), "TimelockController: operation already scheduled");
require(delay >= getMinDelay(), "TimelockController: insufficient delay");
_timestamps[id] = block.timestamp + delay;
}
/**
* @dev Cancel an operation.
*
* Requirements:
*
* - the caller must have the 'canceller' role.
*/
function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
require(isOperationPending(id), "TimelockController: operation cannot be cancelled");
delete _timestamps[id];
emit Cancelled(id);
}
/**
* @dev Execute an (ready) operation containing a single transaction.
*
* Emits a {CallExecuted} event.
*
* Requirements:
*
* - the caller must have the 'executor' role.
*/
// This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
// thus any modifications to the operation during reentrancy should be caught.
// slither-disable-next-line reentrancy-eth
function execute(
address target,
uint256 value,
bytes calldata payload,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
bytes32 id = hashOperation(target, value, payload, predecessor, salt);
_beforeCall(id, predecessor);
_execute(target, value, payload);
emit CallExecuted(id, 0, target, value, payload);
_afterCall(id);
}
/**
* @dev Execute an (ready) operation containing a batch of transactions.
*
* Emits one {CallExecuted} event per transaction in the batch.
*
* Requirements:
*
* - the caller must have the 'executor' role.
*/
// This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
// thus any modifications to the operation during reentrancy should be caught.
// slither-disable-next-line reentrancy-eth
function executeBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == payloads.length, "TimelockController: length mismatch");
bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
_beforeCall(id, predecessor);
for (uint256 i = 0; i < targets.length; ++i) {
address target = targets[i];
uint256 value = values[i];
bytes calldata payload = payloads[i];
_execute(target, value, payload);
emit CallExecuted(id, i, target, value, payload);
}
_afterCall(id);
}
/**
* @dev Execute an operation's call.
*/
function _execute(address target, uint256 value, bytes calldata data) internal virtual {
(bool success, ) = target.call{value: value}(data);
require(success, "TimelockController: underlying transaction reverted");
}
/**
* @dev Checks before execution of an operation's calls.
*/
function _beforeCall(bytes32 id, bytes32 predecessor) private view {
require(isOperationReady(id), "TimelockController: operation is not ready");
require(predecessor == bytes32(0) || isOperationDone(predecessor), "TimelockController: missing dependency");
}
/**
* @dev Checks after execution of an operation's calls.
*/
function _afterCall(bytes32 id) private {
require(isOperationReady(id), "TimelockController: operation is not ready");
_timestamps[id] = _DONE_TIMESTAMP;
}
/**
* @dev Changes the minimum timelock duration for future operations.
*
* Emits a {MinDelayChange} event.
*
* Requirements:
*
* - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
* an operation where the timelock is the target and the data is the ABI-encoded call to this function.
*/
function updateDelay(uint256 newDelay) external virtual {
require(msg.sender == address(this), "TimelockController: caller must be timelock");
emit MinDelayChange(_minDelay, newDelay);
_minDelay = newDelay;
}
/**
* @dev See {IERC721Receiver-onERC721Received}.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
/**
* @dev See {IERC1155Receiver-onERC1155Received}.
*/
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
/**
* @dev See {IERC1155Receiver-onERC1155BatchReceived}.
*/
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}
@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma abicoder v2;
import './plugin/IAlgebraPluginFactory.sol';
import './vault/IAlgebraVaultFactory.sol';
/// @title The interface for the Algebra Factory
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraFactory {
/// @notice Emitted when a process of ownership renounce is started
/// @param timestamp The timestamp of event
/// @param finishTimestamp The timestamp when ownership renounce will be possible to finish
event RenounceOwnershipStart(uint256 timestamp, uint256 finishTimestamp);
/// @notice Emitted when a process of ownership renounce cancelled
/// @param timestamp The timestamp of event
event RenounceOwnershipStop(uint256 timestamp);
/// @notice Emitted when a process of ownership renounce finished
/// @param timestamp The timestamp of ownership renouncement
event RenounceOwnershipFinish(uint256 timestamp);
/// @notice Emitted when a pool is created
/// @param token0 The first token of the pool by address sort order
/// @param token1 The second token of the pool by address sort order
/// @param pool The address of the created pool
event Pool(address indexed token0, address indexed token1, address pool);
/// @notice Emitted when the default community fee is changed
/// @param newDefaultCommunityFee The new default community fee value
event DefaultCommunityFee(uint16 newDefaultCommunityFee);
/// @notice Emitted when the default tickspacing is changed
/// @param newDefaultTickspacing The new default tickspacing value
event DefaultTickspacing(int24 newDefaultTickspacing);
/// @notice Emitted when the default fee is changed
/// @param newDefaultFee The new default fee value
event DefaultFee(uint16 newDefaultFee);
/// @notice Emitted when the defaultPluginFactory address is changed
/// @param defaultPluginFactoryAddress The new defaultPluginFactory address
event DefaultPluginFactory(address defaultPluginFactoryAddress);
/// @notice Emitted when the vaultFactory address is changed
/// @param newVaultFactory The new vaultFactory address
event VaultFactory(address newVaultFactory);
/// @notice Emitted when the pools creation mode is changed
/// @param mode_ The new pools creation mode
event PublicPoolCreationMode(bool mode_);
/// @notice role that can change communityFee and tickspacing in pools
/// @return The hash corresponding to this role
function POOLS_ADMINISTRATOR_ROLE() external view returns (bytes32);
/// @notice role that can create pools when public pool creation is disabled
/// @return The hash corresponding to this role
function POOLS_CREATOR_ROLE() external view returns (bytes32);
/// @notice Returns `true` if `account` has been granted `role` or `account` is owner.
/// @param role The hash corresponding to the role
/// @param account The address for which the role is checked
/// @return bool Whether the address has this role or the owner role or not
function hasRoleOrOwner(bytes32 role, address account) external view returns (bool);
/// @notice Returns the current owner of the factory
/// @dev Can be changed by the current owner via transferOwnership(address newOwner)
/// @return The address of the factory owner
function owner() external view returns (address);
/// @notice Returns the current poolDeployerAddress
/// @return The address of the poolDeployer
function poolDeployer() external view returns (address);
/// @notice Returns the status of enable public pool creation mode
/// @return bool Whether the public creation mode is enable or not
function isPublicPoolCreationMode() external view returns (bool);
/// @notice Returns the default community fee
/// @return Fee which will be set at the creation of the pool
function defaultCommunityFee() external view returns (uint16);
/// @notice Returns the default fee
/// @return Fee which will be set at the creation of the pool
function defaultFee() external view returns (uint16);
/// @notice Returns the default tickspacing
/// @return Tickspacing which will be set at the creation of the pool
function defaultTickspacing() external view returns (int24);
/// @notice Return the current pluginFactory address
/// @dev This contract is used to automatically set a plugin address in new liquidity pools
/// @return Algebra plugin factory
function defaultPluginFactory() external view returns (IAlgebraPluginFactory);
/// @notice Return the current vaultFactory address
/// @dev This contract is used to automatically set a vault address in new liquidity pools
/// @return Algebra vault factory
function vaultFactory() external view returns (IAlgebraVaultFactory);
/// @notice Returns the default communityFee, tickspacing, fee and communityFeeVault for pool
/// @param pool the address of liquidity pool
/// @return communityFee which will be set at the creation of the pool
/// @return tickSpacing which will be set at the creation of the pool
/// @return fee which will be set at the creation of the pool
/// @return communityFeeVault the address of communityFeeVault
function defaultConfigurationForPool(
address pool
) external view returns (uint16 communityFee, int24 tickSpacing, uint16 fee, address communityFeeVault);
/// @notice Deterministically computes the pool address given the token0 and token1
/// @dev The method does not check if such a pool has been created
/// @param token0 first token
/// @param token1 second token
/// @return pool The contract address of the Algebra pool
function computePoolAddress(address token0, address token1) external view returns (address pool);
/// @notice Returns the pool address for a given pair of tokens, or address 0 if it does not exist
/// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
/// @param tokenA The contract address of either token0 or token1
/// @param tokenB The contract address of the other token
/// @return pool The pool address
function poolByPair(address tokenA, address tokenB) external view returns (address pool);
/// @notice returns keccak256 of AlgebraPool init bytecode.
/// @dev the hash value changes with any change in the pool bytecode
/// @return Keccak256 hash of AlgebraPool contract init bytecode
function POOL_INIT_CODE_HASH() external view returns (bytes32);
/// @return timestamp The timestamp of the beginning of the renounceOwnership process
function renounceOwnershipStartTimestamp() external view returns (uint256 timestamp);
/// @notice Creates a pool for the given two tokens
/// @param tokenA One of the two tokens in the desired pool
/// @param tokenB The other of the two tokens in the desired pool
/// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0.
/// The call will revert if the pool already exists or the token arguments are invalid.
/// @return pool The address of the newly created pool
function createPool(address tokenA, address tokenB) external returns (address pool);
/// @dev updates pools creation mode
/// @param mode_ the new mode for pools creation proccess
function setIsPublicPoolCreationMode(bool mode_) external;
/// @dev updates default community fee for new pools
/// @param newDefaultCommunityFee The new community fee, _must_ be <= MAX_COMMUNITY_FEE
function setDefaultCommunityFee(uint16 newDefaultCommunityFee) external;
/// @dev updates default fee for new pools
/// @param newDefaultFee The new fee, _must_ be <= MAX_DEFAULT_FEE
function setDefaultFee(uint16 newDefaultFee) external;
/// @dev updates default tickspacing for new pools
/// @param newDefaultTickspacing The new tickspacing, _must_ be <= MAX_TICK_SPACING and >= MIN_TICK_SPACING
function setDefaultTickspacing(int24 newDefaultTickspacing) external;
/// @dev updates pluginFactory address
/// @param newDefaultPluginFactory address of new plugin factory
function setDefaultPluginFactory(address newDefaultPluginFactory) external;
/// @dev updates vaultFactory address
/// @param newVaultFactory address of new vault factory
function setVaultFactory(address newVaultFactory) external;
/// @notice Starts process of renounceOwnership. After that, a certain period
/// of time must pass before the ownership renounce can be completed.
function startRenounceOwnership() external;
/// @notice Stops process of renounceOwnership and removes timer.
function stopRenounceOwnership() external;
}
contracts/mocks/MinterMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
contract MinterMock {
uint256 public constant WEEK = 86400 * 7;
uint256 public active_period;
function setPeriod(uint256 period_) external {
active_period = period_;
}
function period() public view returns (uint256) {
return (block.timestamp / WEEK) * WEEK;
}
}
@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolErrors.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4;
/// @title Errors emitted by a pool
/// @notice Contains custom errors emitted by the pool
/// @dev Custom errors are separated from the common pool interface for compatibility with older versions of Solidity
interface IAlgebraPoolErrors {
// #### pool errors ####
/// @notice Emitted by the reentrancy guard
error locked();
/// @notice Emitted if arithmetic error occurred
error arithmeticError();
/// @notice Emitted if an attempt is made to initialize the pool twice
error alreadyInitialized();
/// @notice Emitted if an attempt is made to mint or swap in uninitialized pool
error notInitialized();
/// @notice Emitted if 0 is passed as amountRequired to swap function
error zeroAmountRequired();
/// @notice Emitted if invalid amount is passed as amountRequired to swap function
error invalidAmountRequired();
/// @notice Emitted if the pool received fewer tokens than it should have
error insufficientInputAmount();
/// @notice Emitted if there was an attempt to mint zero liquidity
error zeroLiquidityDesired();
/// @notice Emitted if actual amount of liquidity is zero (due to insufficient amount of tokens received)
error zeroLiquidityActual();
/// @notice Emitted if the pool received fewer tokens0 after flash than it should have
error flashInsufficientPaid0();
/// @notice Emitted if the pool received fewer tokens1 after flash than it should have
error flashInsufficientPaid1();
/// @notice Emitted if limitSqrtPrice param is incorrect
error invalidLimitSqrtPrice();
/// @notice Tick must be divisible by tickspacing
error tickIsNotSpaced();
/// @notice Emitted if a method is called that is accessible only to the factory owner or dedicated role
error notAllowed();
/// @notice Emitted if new tick spacing exceeds max allowed value
error invalidNewTickSpacing();
/// @notice Emitted if new community fee exceeds max allowed value
error invalidNewCommunityFee();
/// @notice Emitted if an attempt is made to manually change the fee value, but dynamic fee is enabled
error dynamicFeeActive();
/// @notice Emitted if an attempt is made by plugin to change the fee value, but dynamic fee is disabled
error dynamicFeeDisabled();
/// @notice Emitted if an attempt is made to change the plugin configuration, but the plugin is not connected
error pluginIsNotConnected();
/// @notice Emitted if a plugin returns invalid selector after hook call
/// @param expectedSelector The expected selector
error invalidHookResponse(bytes4 expectedSelector);
// #### LiquidityMath errors ####
/// @notice Emitted if liquidity underflows
error liquiditySub();
/// @notice Emitted if liquidity overflows
error liquidityAdd();
// #### TickManagement errors ####
/// @notice Emitted if the topTick param not greater then the bottomTick param
error topTickLowerOrEqBottomTick();
/// @notice Emitted if the bottomTick param is lower than min allowed value
error bottomTickLowerThanMIN();
/// @notice Emitted if the topTick param is greater than max allowed value
error topTickAboveMAX();
/// @notice Emitted if the liquidity value associated with the tick exceeds MAX_LIQUIDITY_PER_TICK
error liquidityOverflow();
/// @notice Emitted if an attempt is made to interact with an uninitialized tick
error tickIsNotInitialized();
/// @notice Emitted if there is an attempt to insert a new tick into the list of ticks with incorrect indexes of the previous and next ticks
error tickInvalidLinks();
// #### SafeTransfer errors ####
/// @notice Emitted if token transfer failed internally
error transferFailed();
// #### TickMath errors ####
/// @notice Emitted if tick is greater than the maximum or less than the minimum allowed value
error tickOutOfRange();
/// @notice Emitted if price is greater than the maximum or less than the minimum allowed value
error priceOutOfRange();
}
@openzeppelin/contracts/utils/Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.0;
import "../ERC1967/ERC1967Proxy.sol";
/**
* @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
* does not implement this interface directly, and some of its functions are implemented by an internal dispatch
* mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
* include them in the ABI so this interface must be used to interact with it.
*/
interface ITransparentUpgradeableProxy is IERC1967 {
function admin() external view returns (address);
function implementation() external view returns (address);
function changeAdmin(address) external;
function upgradeTo(address) external;
function upgradeToAndCall(address, bytes memory) external payable;
}
/**
* @dev This contract implements a proxy that is upgradeable by an admin.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches one of the admin functions exposed by the proxy itself.
* 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
* implementation. If the admin tries to call a function on the implementation it will fail with an error that says
* "admin cannot fallback to proxy target".
*
* These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
* the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
* to sudden errors when trying to call a function from the proxy implementation.
*
* Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
* you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
*
* NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
* inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch
* mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
* fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
* implementation.
*
* WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler
* will not check that there are no selector conflicts, due to the note above. A selector clash between any new function
* and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could
* render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
/**
* @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
* optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
*/
constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
_changeAdmin(admin_);
}
/**
* @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
*
* CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the
* implementation provides a function with the same selector.
*/
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior
*/
function _fallback() internal virtual override {
if (msg.sender == _getAdmin()) {
bytes memory ret;
bytes4 selector = msg.sig;
if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) {
ret = _dispatchUpgradeTo();
} else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
ret = _dispatchUpgradeToAndCall();
} else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) {
ret = _dispatchChangeAdmin();
} else if (selector == ITransparentUpgradeableProxy.admin.selector) {
ret = _dispatchAdmin();
} else if (selector == ITransparentUpgradeableProxy.implementation.selector) {
ret = _dispatchImplementation();
} else {
revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
}
assembly {
return(add(ret, 0x20), mload(ret))
}
} else {
super._fallback();
}
}
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function _dispatchAdmin() private returns (bytes memory) {
_requireZeroValue();
address admin = _getAdmin();
return abi.encode(admin);
}
/**
* @dev Returns the current implementation.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function _dispatchImplementation() private returns (bytes memory) {
_requireZeroValue();
address implementation = _implementation();
return abi.encode(implementation);
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _dispatchChangeAdmin() private returns (bytes memory) {
_requireZeroValue();
address newAdmin = abi.decode(msg.data[4:], (address));
_changeAdmin(newAdmin);
return "";
}
/**
* @dev Upgrade the implementation of the proxy.
*/
function _dispatchUpgradeTo() private returns (bytes memory) {
_requireZeroValue();
address newImplementation = abi.decode(msg.data[4:], (address));
_upgradeToAndCall(newImplementation, bytes(""), false);
return "";
}
/**
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract.
*/
function _dispatchUpgradeToAndCall() private returns (bytes memory) {
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
_upgradeToAndCall(newImplementation, data, true);
return "";
}
/**
* @dev Returns the current admin.
*
* CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead.
*/
function _admin() internal view virtual returns (address) {
return _getAdmin();
}
/**
* @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to
* emulate some proxy functions being non-payable while still allowing value to pass through.
*/
function _requireZeroValue() private {
require(msg.value == 0);
}
}
@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/core/interfaces/IVeBoost.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title Interface for VeBoost
* @dev Interface for boosting functionality within the Lute ecosystem.
*/
interface IVeBoost {
/**
* @dev Emitted when a reward is sent to a token owner as part of the boost process.
* @param token The address of the reward token.
* @param recipient The recipient of the reward.
* @param rewardTokenBoostAmount The amount of reward token sent.
*/
event RewardSent(address indexed token, address indexed recipient, uint256 indexed rewardTokenBoostAmount);
/**
* @dev Emitted when the LUTE boost percentage is updated.
* @param luteBoostPercentage New boost percentage.
*/
event LUTEBoostPercentage(uint256 indexed luteBoostPercentage);
/**
* @dev Emitted when the minimum USD amount required for a boost is updated.
* @param minUSDAmount New minimum USD amount.
*/
event MinUSDAmount(uint256 indexed minUSDAmount);
/**
* @dev Emitted when the minimum locked time for a boost is updated.
* @param minLockedTime_ New minimum locked time.
*/
event MinLockedTime(uint256 indexed minLockedTime_);
/**
* @dev Emitted when tokens are recovered by the owner.
* @param token Address of the recovered token.
* @param recoverAmount Amount of tokens recovered.
*/
event RecoverToken(address indexed token, uint256 indexed recoverAmount);
/**
* @dev Emitted when a new reward token is added.
* @param token Address of the reward token added.
*/
event AddRewardToken(address indexed token);
/**
* @dev Emitted when a reward token is removed.
* @param token Address of the reward token removed.
*/
event RemoveRewardToken(address indexed token);
/**
* @dev Emitted when a new price provider is setted.
* @param priceProvider Address of the new price provider.
*/
event PriceProvider(address indexed priceProvider);
// Errors
error InvalidMinLockedTime();
error AccessDenied();
error RewardTokenExist();
error RewardTokenNotExist();
error InvalidBoostAmount();
/**
* @dev Before paying LUTE boost, checks if the boost amount is valid and then distributes reward tokens proportionally.
* Can only be called by the voting escrow contract. Emits `InvalidBoostAmount` error if conditions are not met.
* @param tokenOwner_ The owner of the tokens to receive the boost.
* @param tokenId_ The ID of the token to be boosted.
* @param depositedLUTEAmount_ The amount of LUTE that was deposited.
* @param paidBoostLUTEAmount_ The amount of LUTE used for the boost.
*/
function beforeLUTEBoostPaid(address tokenOwner_, uint256 tokenId_, uint256 depositedLUTEAmount_, uint256 paidBoostLUTEAmount_) external;
/**
* @dev Returns the minimum LUTE amount required for receiving a boost.
* @return The minimum amount of LUTE required for a boost.
*/
function getMinLUTEAmountForBoost() external view returns (uint256);
/**
* @dev Returns the minimum locked time required to qualify for a boost.
* @return The minimum locked time in seconds.
*/
function getMinLockedTimeForBoost() external view returns (uint256);
/**
* @dev Calculates the amount of LUTE that can be boosted based on the deposited amount.
* @param depositedLUTEAmount_ The amount of LUTE deposited.
* @return The amount of LUTE that will be boosted.
*/
function calculateBoostLUTEAmount(uint256 depositedLUTEAmount_) external view returns (uint256);
/**
* @dev Returns the available amount of LUTE for boosts, considering both balance and allowance.
* @return The available LUTE amount for boosts.
*/
function getAvailableBoostLUTEAmount() external view returns (uint256);
}
contracts/bribes/rewards/CustomBribeRewardRouter.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {IERC20Upgradeable, SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ERC721HolderUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol";
import {IVotingEscrow} from "../../core/interfaces/IVotingEscrow.sol";
import {IVoter} from "../../core/interfaces/IVoter.sol";
import {IBribe} from "../interfaces/IBribe.sol";
import {IBribeVeLUTERewardToken} from "./interfaces/IBribeVeLUTERewardToken.sol";
import {ICustomBribeRewardRouter} from "./interfaces/ICustomBribeRewardRouter.sol";
/**
* @title CustomBribeRewardRouter
* @notice This contract facilitates the distribution of LUTE-based rewards into external bribe contracts
* as veLUTE-based intermediary tokens. It converts either direct LUTE deposits or veLUTE NFTs
* (burned to reclaim underlying LUTE) into brVeLUTE tokens, and then notifies external bribe contracts
* of these new rewards.
*
* @dev This contract:
* - Inherits from ICustomBribeRewardRouter and provides implementations for LUTE to brVeLUTE reward distribution.
* - Allows enabling/disabling certain functions via `funcEnabled` mapping controlled by an admin role.
* - Uses a voter contract to derive the correct external bribe contract for a given pool.
* - Requires the caller to have appropriate roles and the function to be enabled before executing certain operations.
*/
contract CustomBribeRewardRouter is ICustomBribeRewardRouter, AccessControlUpgradeable, ERC721HolderUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @notice The address of the intermediate bribe-veLUTE reward token.
address public bribeVeLuteRewardToken;
/// @notice The address of the voter contract used to map pools to gauges and thus to external bribe contracts.
address public voter;
/// @notice A mapping of function selectors to a boolean indicating if the function is enabled.
mapping(bytes4 => bool) public funcEnabled;
/// @dev Thrown when attempting to call a function that has been disabled.
error FunctionDisabled();
/// @dev Thrown when the provided pool does not map to a valid gauge or external bribe contract.
error InvalidPool(address pool);
/// @dev Thrown when the retrieved external bribe contract is invalid (e.g., zero address).
error InvalidBribe(address bribe);
/**
* @dev Modifier that checks whether the given function selector is enabled before proceeding.
* Reverts with `FunctionDisabled()` if not enabled.
* @param funcSign_ The 4-byte function selector.
*/
modifier whenEnabled(bytes4 funcSign_) {
if (!funcEnabled[funcSign_]) {
revert FunctionDisabled();
}
_;
}
/**
* @notice Constructor for UUPS pattern. The main logic is in the `initialize` function.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract.
* @dev Grants DEFAULT_ADMIN_ROLE to the caller. Sets the voter and bribeVeLuteRewardToken addresses.
* @param voter_ The address of the voter contract used to map pools to gauges and external bribes.
* @param bribeVeLuteRewardToken_ The address of the brVeLUTE token contract.
*/
function initialize(address voter_, address bribeVeLuteRewardToken_) external initializer {
__AccessControl_init();
__ERC721Holder_init();
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
voter = voter_;
bribeVeLuteRewardToken = bribeVeLuteRewardToken_;
}
/**
* @notice Enables or disables a specific function based on its selector.
* @dev Only callable by addresses with DEFAULT_ADMIN_ROLE.
* @param funcSign_ The function selector for which the state is being changed.
* @param isEnable_ True to enable the function, false to disable.
*
* Emits a {FuncEnabled} event.
*/
function setupFuncEnable(bytes4 funcSign_, bool isEnable_) external onlyRole(DEFAULT_ADMIN_ROLE) {
funcEnabled[funcSign_] = isEnable_;
emit FuncEnabled(funcSign_, isEnable_);
}
/**
* @notice Notifies an external bribe contract of LUTE-based rewards by converting LUTE into brVeLUTE tokens.
* @dev LUTE tokens are transferred from the caller to this contract, then converted into brVeLUTE tokens,
* and finally notified to the external bribe contract associated with the given pool.
* @param pool_ The address of the pool for which the reward is being distributed.
* @param amount_ The amount of LUTE to convert and distribute as brVeLUTE rewards.
*
* Emits a {NotifyRewardLUTEInVeLute} event.
* Reverts if the function is disabled or the pool is invalid.
*/
function notifyRewardLUTEInVeLUTE(
address pool_,
uint256 amount_
) external whenEnabled(ICustomBribeRewardRouter.notifyRewardLUTEInVeLUTE.selector) {
IBribeVeLUTERewardToken bribeVeLuteRewardTokenCache = IBribeVeLUTERewardToken(bribeVeLuteRewardToken);
IERC20Upgradeable token = IERC20Upgradeable(bribeVeLuteRewardTokenCache.underlyingToken());
token.safeTransferFrom(_msgSender(), address(this), amount_);
token.forceApprove(address(bribeVeLuteRewardTokenCache), amount_);
bribeVeLuteRewardTokenCache.mint(address(this), amount_);
address externalBribe = _getExternalBribe(pool_);
IERC20Upgradeable(bribeVeLuteRewardTokenCache).forceApprove(externalBribe, amount_);
IBribe(externalBribe).notifyRewardAmount(address(bribeVeLuteRewardTokenCache), amount_);
emit NotifyRewardLUTEInVeLute(_msgSender(), pool_, externalBribe, amount_);
}
/**
* @notice Notifies an external bribe contract using LUTE reclaimed from burning a veLUTE NFT.
* @dev A veLUTE NFT is transferred from the caller to this contract, burned to reclaim LUTE,
* then converted into brVeLUTE, and finally notified to the external bribe contract.
* @param pool_ The address of the pool for which the reward is being distributed.
* @param tokenId_ The ID of the veLUTE NFT to be burned to reclaim LUTE.
*
* Emits a {NotifyRewardVeLUTEInVeLute} event.
* Reverts if the function is disabled, the pool is invalid, or the NFT is not eligible to be burned.
*/
function notifyRewardVeLUTEInVeLute(
address pool_,
uint256 tokenId_
) external whenEnabled(ICustomBribeRewardRouter.notifyRewardVeLUTEInVeLute.selector) {
IBribeVeLUTERewardToken bribeVeLuteRewardTokenCache = IBribeVeLUTERewardToken(bribeVeLuteRewardToken);
IERC20Upgradeable token = IERC20Upgradeable(bribeVeLuteRewardTokenCache.underlyingToken());
IVotingEscrow votingEscrow = IVotingEscrow(bribeVeLuteRewardTokenCache.votingEscrow());
votingEscrow.safeTransferFrom(_msgSender(), address(this), tokenId_, "");
uint256 balanceBefore = token.balanceOf(address(this));
if (votingEscrow.getNftState(tokenId_).locked.isPermanentLocked) {
votingEscrow.unlockPermanent(tokenId_);
}
votingEscrow.burnToBribes(tokenId_);
uint256 amount = token.balanceOf(address(this)) - balanceBefore;
token.forceApprove(address(bribeVeLuteRewardTokenCache), amount);
bribeVeLuteRewardTokenCache.mint(address(this), amount);
address externalBribe = _getExternalBribe(pool_);
IERC20Upgradeable(bribeVeLuteRewardTokenCache).forceApprove(externalBribe, amount);
IBribe(externalBribe).notifyRewardAmount(address(bribeVeLuteRewardTokenCache), amount);
emit NotifyRewardVeLUTEInVeLute(_msgSender(), pool_, externalBribe, tokenId_, amount);
}
/**
* @dev Retrieves the external bribe contract associated with a given pool via the voter contract.
* @param pool_ The address of the pool to fetch the external bribe for.
* @return externalBribe The address of the associated external bribe contract.
*
* Reverts if:
* - No gauge is found for the given pool.
* - The gauge is alive (not a finalized state required for bribing).
* - No external bribe contract is found.
*/
function _getExternalBribe(address pool_) internal view returns (address) {
IVoter voterCache = IVoter(voter);
address gauge = voterCache.poolToGauge(pool_);
if (gauge == address(0)) {
revert InvalidPool(pool_);
}
if (!voterCache.isAlive(gauge)) {
revert InvalidPool(pool_);
}
address externalBribe = voterCache.getGaugeState(gauge).externalBribe;
if (externalBribe == address(0)) {
revert InvalidBribe(externalBribe);
}
return externalBribe;
}
}
contracts/lute/RouterV2PathProviderUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {IRouterV2} from "../dexV2/interfaces/IRouterV2.sol";
import {IPairFactory} from "../dexV2/interfaces/IPairFactory.sol";
import {IRouterV2PathProvider} from "./interfaces/IRouterV2PathProvider.sol";
import {IPairQuote} from "./interfaces/IPairQuote.sol";
/**
* @title Router V2 Path Provider Upgradeable
* @notice Provides management for token routing paths within a decentralized exchange platform.
* @dev Utilizes upgradeable patterns from OpenZeppelin, including Ownable and Initializable functionalities.
*/
contract RouterV2PathProviderUpgradeable is IRouterV2PathProvider, Ownable2StepUpgradeable {
/**
* @notice Constant used to specify the granularity of quotes in getAmountOutQuote
*/
uint256 public constant PAIR_QUOTE_GRANULARITY = 3;
/**
* @notice Address of the router used for fetching and calculating routes
* @dev This should be set to the address of the router managing the routes and their calculations.
*/
address public override router;
/**
* @notice Address of the factory used for managing pair creations
* @dev This should be set to the address of the factory responsible for creating and managing token pairs.
*/
address public override factory;
/**
* @notice Mapping of tokens to their permission status for being used in input routes
* @dev True if the token is allowed to be used in input routes, false otherwise. This is checked during route validation.
*/
mapping(address => bool) public override isAllowedTokenInInputRoutes;
/**
* @notice Mapping of tokens to their associated routing paths
* @dev Stores an array of `IRouterV2.route` structs for each token, representing the possible routes for token exchange.
*/
mapping(address => IRouterV2.route[]) public tokenToRoutes;
/**
* @notice Custom error for signaling issues with the path in route calculations
* @dev This error is thrown when there is a discontinuity in the route path, indicating a misconfiguration.
*/
error InvalidPath();
/**
* @notice Custom error for signaling invalid route configurations
* @dev This error is thrown when a route configuration does not meet the required criteria, such as incorrect token addresses or settings.
*/
error InvalidRoute();
/**
* @notice Custom error for signaling when a route does not exist in the mapping
* @dev This error is thrown when an attempt is made to access or modify a non-existent route in the tokenToRoutes mapping.
*/
error RouteNotExist();
/**
* @notice Custom error for signaling when a route already exists in the mapping
* @dev This error is thrown when there is an attempt to add a route that already exists in the tokenToRoutes mapping, to prevent duplicates.
*/
error RouteAlreadyExist();
error AddressZero();
/**
* @notice Disables initialization on the implementation to prevent proxy issues.
* @dev Constructor sets up non-initializable pattern for proxy use.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract with necessary governance and operational addresses
* @dev Sets up operational aspects of the contract. This function can only be called once.
*
* @param factory_ The factory address used to manage pairings
* @param router_ The router address used to manage routing logic
*/
function initialize(address factory_, address router_) external initializer {
_checkAddressZero(factory_);
_checkAddressZero(router_);
__Ownable2Step_init();
factory = factory_;
router = router_;
}
/**
* @notice Sets whether a token can be used in input routes
* @dev Only callable by the owner. Emits a SetAllowedTokenInInputRouters event on change.
*
* @param token_ The token address to set the allowance for
* @param isAllowed_ Boolean flag to allow or disallow the token
*/
function setAllowedTokenInInputRouters(address token_, bool isAllowed_) external onlyOwner {
isAllowedTokenInInputRoutes[token_] = isAllowed_;
emit SetAllowedTokenInInputRouters(token_, isAllowed_);
}
/**
* @notice Adds a new route to a token
* @dev Verifies route validity and uniqueness before addition. Reverts on errors.
*
* @param token_ The token address to add the route to
* @param route_ The route to add
*/
function addRouteToToken(address token_, IRouterV2.route memory route_) external onlyOwner {
_checkAddressZero(token_);
_checkAddressZero(route_.from);
if (token_ != route_.to || token_ == route_.from) {
revert InvalidRoute();
}
uint256 length = tokenToRoutes[token_].length;
for (uint256 i; i < length; ) {
if (tokenToRoutes[token_][i].from == route_.from && tokenToRoutes[token_][i].stable == route_.stable) {
revert RouteAlreadyExist();
}
unchecked {
i++;
}
}
tokenToRoutes[token_].push(route_);
emit AddRouteToToken(token_, route_);
}
/**
* @notice Removes a route from a token
* @dev Verifies the existence of the route before removal. Emits RemoveRouteFromToken event on success.
*
* @param token_ The token address to remove the route from
* @param route_ The route to remove
*/
function removeRouteFromToken(address token_, IRouterV2.route memory route_) external onlyOwner {
_checkAddressZero(token_);
_checkAddressZero(route_.from);
if (token_ != route_.to || token_ == route_.from) {
revert InvalidRoute();
}
uint256 length = tokenToRoutes[token_].length;
for (uint256 i; i < length; ) {
if (tokenToRoutes[token_][i].from == route_.from && tokenToRoutes[token_][i].stable == route_.stable) {
tokenToRoutes[token_][i] = tokenToRoutes[token_][length - 1];
tokenToRoutes[token_].pop();
emit RemoveRouteFromToken(token_, route_);
return;
}
unchecked {
i++;
}
}
revert RouteNotExist();
}
/**
* @notice Retrieves all routes associated with a specific token
* @dev Returns an array of routes for a given token address.
*
* @param token_ The token address to retrieve routes for
* @return An array of IRouterV2.route structures
*/
function getTokenRoutes(address token_) external view returns (IRouterV2.route[] memory) {
return tokenToRoutes[token_];
}
/**
* @notice Retrieves all possible routes between two tokens
* @dev Returns a two-dimensional array of routes for possible paths from inputToken_ to outputToken_
*
* @param inputToken_ The address of the input token
* @param outputToken_ The address of the output token
* @return A two-dimensional array of routes
*/
function getRoutesTokenToToken(address inputToken_, address outputToken_) external view returns (IRouterV2.route[][] memory) {
return _getRoutesTokenToToken(inputToken_, outputToken_);
}
/**
* @notice Determines the optimal route and expected output amount for a token pair given an input amount
* @dev Searches through all possible routes to find the one that provides the highest output amount
*
* @param inputToken_ The address of the input token
* @param outputToken_ The address of the output token
* @param amountIn_ The amount of input tokens to trade
* @return A tuple containing the optimal route and the amount out
*/
function getOptimalTokenToTokenRoute(
address inputToken_,
address outputToken_,
uint256 amountIn_
) external view returns (IRouterV2.route[] memory, uint256 amountOut) {
IPairFactory factoryCache = IPairFactory(factory);
IRouterV2 routerCache = IRouterV2(router);
IRouterV2.route[][] memory routesTokenToToken = _getRoutesTokenToToken(inputToken_, outputToken_);
uint256 index;
uint256 bestMultiRouteAmountOut;
for (uint256 i; i < routesTokenToToken.length; ) {
if (
factoryCache.getPair(routesTokenToToken[i][0].from, routesTokenToToken[i][0].to, routesTokenToToken[i][0].stable) !=
address(0)
) {
try routerCache.getAmountsOut(amountIn_, routesTokenToToken[i]) returns (uint256[] memory amountsOut) {
if (amountsOut[2] > bestMultiRouteAmountOut) {
bestMultiRouteAmountOut = amountsOut[2];
index = i;
}
} catch {}
}
unchecked {
i++;
}
}
IRouterV2.route[] memory singelRoute = new IRouterV2.route[](1);
uint256 amountOutStabel;
uint256 amountOutVolatility;
if (factoryCache.getPair(inputToken_, outputToken_, true) != address(0)) {
singelRoute[0] = IRouterV2.route({from: inputToken_, to: outputToken_, stable: true});
try routerCache.getAmountsOut(amountIn_, singelRoute) returns (uint256[] memory amountsOut) {
amountOutStabel = amountsOut[1];
} catch {}
}
if (factoryCache.getPair(inputToken_, outputToken_, false) != address(0)) {
singelRoute[0] = IRouterV2.route({from: inputToken_, to: outputToken_, stable: false});
try routerCache.getAmountsOut(amountIn_, singelRoute) returns (uint256[] memory amountsOut) {
amountOutVolatility = amountsOut[1];
} catch {}
}
if (amountOutVolatility >= amountOutStabel && amountOutVolatility >= bestMultiRouteAmountOut) {
if (amountOutVolatility == 0) {
return (new IRouterV2.route[](0), 0);
}
return (singelRoute, amountOutVolatility);
} else if (amountOutStabel >= amountOutVolatility && amountOutStabel >= bestMultiRouteAmountOut) {
singelRoute[0] = IRouterV2.route({from: inputToken_, to: outputToken_, stable: true});
return (singelRoute, amountOutStabel);
} else {
return (routesTokenToToken[index], bestMultiRouteAmountOut);
}
}
/**
* @notice Calculates the output amount for a specified route given an amount of input tokens, using granular quoting
* @dev This function extends getAmountOut by incorporating quoting functionality, which factors in additional parameters like granularity.
*
* @param amountIn_ The amount of input tokens
* @param routes_ The routes to be evaluated
* @return The output amount of tokens after trading along the specified routes
*/
function getAmountOutQuote(uint256 amountIn_, IRouterV2.route[] calldata routes_) external view returns (uint256) {
if (routes_.length == 0) {
revert InvalidPath();
}
for (uint256 i; i < routes_.length - 1; ) {
if (routes_[i].to != routes_[i + 1].from) {
revert InvalidPath();
}
unchecked {
i++;
}
}
IPairFactory pairFactoryCache = IPairFactory(factory);
for (uint256 i; i < routes_.length; ) {
address pair = pairFactoryCache.getPair(routes_[i].from, routes_[i].to, routes_[i].stable);
if (pair == address(0)) {
return 0;
}
amountIn_ = IPairQuote(pair).quote(routes_[i].from, amountIn_, PAIR_QUOTE_GRANULARITY);
unchecked {
i++;
}
}
return amountIn_;
}
/**
* @notice Checks if all routes in a provided array are valid according to the contract's rules
* @dev Iterates through each route in the array to check if they are allowed in input routes.
*
* @param inputRouters_ An array of routes to be validated
* @return True if all routes are valid, false otherwise
*/
function isValidInputRoutes(IRouterV2.route[] calldata inputRouters_) external view returns (bool) {
for (uint256 i; i < inputRouters_.length; ) {
if (!isAllowedTokenInInputRoutes[inputRouters_[i].from]) {
return false;
}
unchecked {
i++;
}
}
return true;
}
/**
* @notice Retrieves a list of possible routes between two tokens, considering stable and volatile routes
* @dev This function calculates and returns all viable routes between `inputToken_` and `outputToken_` considering both stability settings.
* It first determines the number of potential routes and then populates them with both stable and volatile path options.
*
* @param inputToken_ The address of the input token for which routes are being sought.
* @param outputToken_ The address of the output token to which routes are being mapped.
* @return routes A two-dimensional array of `IRouterV2.route`, where each primary array entry contains two routes: one stable and one volatile.
*/
function _getRoutesTokenToToken(address inputToken_, address outputToken_) internal view returns (IRouterV2.route[][] memory routes) {
IRouterV2.route[] memory tokenRoutes = tokenToRoutes[outputToken_];
uint256 actualSize;
for (uint256 i; i < tokenRoutes.length; ) {
if (outputToken_ == tokenRoutes[i].to && inputToken_ != tokenRoutes[i].from) {
actualSize++;
}
unchecked {
i++;
}
}
routes = new IRouterV2.route[][](actualSize * 2);
uint256 count;
for (uint256 i; i < tokenRoutes.length; ) {
IRouterV2.route memory route = tokenToRoutes[outputToken_][i];
if (outputToken_ == route.to && inputToken_ != route.from) {
routes[count] = new IRouterV2.route[](2);
routes[count + 1] = new IRouterV2.route[](2);
routes[count][0] = IRouterV2.route({from: inputToken_, to: route.from, stable: true});
routes[count][1] = route;
routes[count + 1][0] = IRouterV2.route({from: inputToken_, to: route.from, stable: false});
routes[count + 1][1] = route;
unchecked {
count += 2;
}
}
unchecked {
i++;
}
}
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library MathUpgradeable {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
contracts/core/interfaces/ILuteRaise.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title ILuteRaise
* @dev This interfaces for contract manages a token raise with both whitelist and public phases.
* It utilizes Merkle proof verification for whitelist management and ensures various caps and limits are adhered to during the raise.
*/
interface ILuteRaise {
/**
* @notice Emitted when a deposit is made
* @param user The address of the user making the deposit
* @param amount The amount of tokens deposited
*/
event Deposit(address indexed user, uint256 indexed amount);
/**
* @notice Emitted when timestamps are updated
* @param startWhitelistPhaseTimestamp The new timestamp for the start of the whitelist phase
* @param startPublicPhaseTimestamp The new timestamp for the start of the public phase
* @param endPublicPhaseTimestamp The new timestamp for the end of the public phase
* @param startClaimPhaseTimestamp The new timestamp for the start of the claim phase
*/
event UpdateTimestamps(
uint256 indexed startWhitelistPhaseTimestamp,
uint256 indexed startPublicPhaseTimestamp,
uint256 indexed endPublicPhaseTimestamp,
uint256 startClaimPhaseTimestamp
);
/**
* @dev Emitted when a user claims their tokens.
* @param user The address of the user.
* @param claimAmount The total amount of tokens claimed.
* @param toTokenAmount The amount of tokens transferred directly to the user.
* @param toVeNFTAmount The amount of tokens locked as veNft.
* @param tokenId The ID of the veNft lock created.
*/
event Claim(address indexed user, uint256 claimAmount, uint256 toTokenAmount, uint256 toVeNFTAmount, uint256 tokenId);
/**
* @notice Emitted when deposit caps are updated
* @param totalDepositCap The new total deposit cap
* @param whitelistPhaseUserCap The new user cap for the whitelist phase
* @param publicPhaseUserCap The new user cap for the public phase
*/
event UpdateDepositCaps(uint256 indexed totalDepositCap, uint256 indexed whitelistPhaseUserCap, uint256 indexed publicPhaseUserCap);
/**
* @notice Emitted when the whitelist root is updated
* @param root The new whitelist root
*/
event UpdateWhitelistRoot(bytes32 indexed root);
/**
* @notice Emitted when deposits are withdrawn
* @param caller The address of the caller withdrawing the deposits
* @param depositsReciever The address receiving the deposits
* @param amount The amount of tokens withdrawn
*/
event WithdrawDeposits(address indexed caller, address indexed depositsReciever, uint256 indexed amount);
/**
* @notice Emitted when excessive rewards are withdrawn
* @param caller The address of the caller withdrawing the excessive rewards
* @param tokensReciever The address receiving the excessive rewards
* @param amount The amount of rewards tokens withdrawn
*/
event WithdrawExcessiveRewardTokens(address indexed caller, address indexed tokensReciever, uint256 indexed amount);
/**
* @notice Claim tokens after the raise
* @dev Users can claim their reward tokens and veNFTs based on their deposited amount.
* If the user has already claimed, it reverts with `AlreadyClaimed`.
* If the deposited amount is zero, it reverts with `ZeroAmount`.
* If the claim phase not started, it reverts with `ClaimPhaseNotStarted`.
*/
function claim() external;
/**
* @notice Allows users to deposit tokens during the raise
* @param amount_ The amount of tokens to deposit
* @param userCap_ The cap for the user (used for whitelist verification)
* @param proof_ The Merkle proof for verifying the user is whitelisted
*/
function deposit(uint256 amount_, uint256 userCap_, bytes32[] memory proof_) external;
/**
* @notice Withdraws the deposits after the raise is finished
*/
function whithdrawDeposits() external;
/**
* @notice Withdraws the excessive rewards after the raise is finished
*/
function withdrawExcessiveRewardTokens() external;
/**
* @notice Sets the deposit caps
* @param totalDepositCap_ The total deposit cap
* @param whitelistPhaseUserCap_ The user cap for the whitelist phase
* @param publicPhaseUserCap_ The user cap for the public phase
*/
function setDepositCaps(uint256 totalDepositCap_, uint256 whitelistPhaseUserCap_, uint256 publicPhaseUserCap_) external;
/**
* @notice Sets the whitelist root
* @param root_ The new whitelist root
*/
function setWhitelistRoot(bytes32 root_) external;
/**
* @notice Sets the timestamps for the phases
* @param startWhitelistPhaseTimestamp_ The timestamp for the start of the whitelist phase
* @param startPublicPhaseTimestamp_ The timestamp for the start of the public phase
* @param endPublicPhaseTimestamp_ The timestamp for the end of the public phase
* @param startClaimPhaseTimestamp_ The timestamp for the start of the claim phase
*/
function setTimestamps(
uint256 startWhitelistPhaseTimestamp_,
uint256 startPublicPhaseTimestamp_,
uint256 endPublicPhaseTimestamp_,
uint256 startClaimPhaseTimestamp_
) external;
/**
* @notice Checks if a user is whitelisted
* @param user_ The address of the user
* @param userCap_ The cap for the user
* @param proof_ The Merkle proof for verifying the user
* @return True if the user is whitelisted, false otherwise
*/
function isWhitelisted(address user_, uint256 userCap_, bytes32[] memory proof_) external view returns (bool);
/**
* @notice Checks if the whitelist phase is active
* @return True if the whitelist phase is active, false otherwise
*/
function isWhitelistPhase() external view returns (bool);
/**
* @notice Checks if the public phase is active
* @return True if the public phase is active, false otherwise
*/
function isPublicPhase() external view returns (bool);
/**
* @notice Checks if the claim phase is active
* @return True if the claim phase is active, false otherwise
*/
function isClaimPhase() external view returns (bool);
/**
* @notice Gets the reward amounts out based on the deposit amount
* @param depositAmount_ The amount of tokens deposited
* @return toRewardTokenAmount The amount of reward tokens
* @return toVeNftAmount The amount to veNFT token
*/
function getRewardsAmountOut(uint256 depositAmount_) external view returns (uint256 toRewardTokenAmount, uint256 toVeNftAmount);
/**
* @notice Returns whether a user has claimed their tokens
* @param user_ The address of the user
* @return True if the user has claimed their tokens, false otherwise
*/
function isUserClaimed(address user_) external view returns (bool);
/**
* @notice Returns the address of the token being raised
* @return The address of the token
*/
function token() external view returns (address);
/**
* @notice Returns the address of the reward token
* @return The address of the reward token
*/
function rewardToken() external view returns (address);
/**
* @notice Returns the amount of reward tokens per deposit token
* @return The amount of reward tokens per deposit token
*/
function amountOfRewardTokenPerDepositToken() external view returns (uint256);
/**
* @notice Returns percentage of the claimed amount to be locked as veNFT
* @return Percentage of the claimed amount to be locked as veNFT
*/
function toVeNftPercentage() external view returns (uint256);
/**
* @notice Returns the address of the voting escrow
* @return The address of the voting escrow
*/
function votingEscrow() external view returns (address);
/**
* @notice Returns the address that will receive the deposits
* @return The address of the deposits receiver
*/
function depositsReciever() external view returns (address);
/**
* @notice Returns the Merkle root for the whitelist verification
* @return The Merkle root
*/
function whitelistMerklRoot() external view returns (bytes32);
/**
* @notice Returns the timestamp for the start of the whitelist phase
* @return The timestamp for the start of the whitelist phase
*/
function startWhitelistPhaseTimestamp() external view returns (uint256);
/**
* @notice Returns the timestamp for the start of the public phase
* @return The timestamp for the start of the public phase
*/
function startPublicPhaseTimestamp() external view returns (uint256);
/**
* @notice Returns the timestamp for the end of the public phase
* @return The timestamp for the end of the public phase
*/
function endPublicPhaseTimestamp() external view returns (uint256);
/**
* @notice Returns the timestamp for the start of the claim phase
* @return The timestamp for the start of the claim phase
*/
function startClaimPhaseTimestamp() external view returns (uint256);
/**
* @notice Returns the maximum amount a user can deposit during the whitelist phase
* @return The user cap for the whitelist phase
*/
function whitelistPhaseUserCap() external view returns (uint256);
/**
* @notice Returns the maximum amount a user can deposit during the public phase
* @return The user cap for the public phase
*/
function publicPhaseUserCap() external view returns (uint256);
/**
* @notice Returns the total cap for deposits
* @return The total deposit cap
*/
function totalDepositCap() external view returns (uint256);
/**
* @notice Returns the total amount deposited so far
* @return The total amount deposited
*/
function totalDeposited() external view returns (uint256);
/**
* @notice Returns the total amount claimed so far
* @return The total amount claimed
*/
function totalClaimed() external view returns (uint256);
/**
* @notice Returns the amount a specific user has deposited
* @param user_ The address of the user
* @return The amount deposited by the user
*/
function userDeposited(address user_) external view returns (uint256);
/**
* @notice Returns the amount a specific user has deposited during whitelist phase
* @param user_ The address of the user
* @return The amount deposited by the user
*/
function userDepositsWhitelistPhase(address user_) external view returns (uint256);
}
contracts/core/interfaces/IGaugeRewarder.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title IGaugeRewarder
* @dev Interface for the GaugeRewarder contract.
*/
interface IGaugeRewarder {
/**
* @dev Emitted when rewards are notified for a gauge.
* @param caller The address that triggered the reward notification.
* @param gauge The address of the gauge receiving the reward.
* @param epoch The current epoch of the minter.
* @param amount The amount of reward tokens notified.
*/
event NotifyReward(address indexed caller, address indexed gauge, uint256 indexed epoch, uint256 amount);
/**
* @dev Emitted when a reward claim is made.
* @param target The address of the recipient of the claimed reward.
* @param reward The amount of reward claimed.
* @param totalAmount The total claimable amount.
*/
event Claim(address target, uint256 reward, uint256 totalAmount);
/**
* @dev Emitted when the signer address is set.
* @param signer The address of the new signer.
*/
event SetSigner(address indexed signer);
/**
* @notice Sets the signer address for reward claims.
* @param signer_ The address of the new signer.
*/
function setSigner(address signer_) external;
/**
* @notice Notifies a reward for a specified gauge.
* @param gauge_ The address of the gauge to receive the reward.
* @param amount_ The amount of reward tokens.
*/
function notifyReward(address gauge_, uint256 amount_) external;
/**
* @notice Transfers tokens and notifies a reward for a specified gauge.
* @param gauge_ The address of the gauge to receive the reward.
* @param amount_ The amount of reward tokens.
*/
function notifyRewardWithTransfer(address gauge_, uint256 amount_) external;
/**
* @notice Claims rewards on behalf of a specified target address.
* @param target_ The address of the recipient of the claimed reward.
* @param totalAmount_ The total amount of reward being claimed.
* @param deadline_ The expiration time of the claim.
* @param signature_ The signature authorizing the claim.
* @return The amount of reward claimed.
*/
function claimFor(address target_, uint256 totalAmount_, uint256 deadline_, bytes memory signature_) external returns (uint256);
/**
* @notice Claims rewards for the caller.
* @param totalAmount_ The total amount of reward being claimed.
* @param deadline_ The expiration time of the claim.
* @param signature_ The signature authorizing the claim.
* @return The amount of reward claimed.
*/
function claim(uint256 totalAmount_, uint256 deadline_, bytes memory signature_) external returns (uint256);
/**
* @notice Gets the total reward distributed so far.
* @return The total amount of rewards distributed.
*/
function totalRewardDistributed() external view returns (uint256);
/**
* @notice Gets the total reward claimed so far.
* @return The total amount of rewards claimed.
*/
function totalRewardClaimed() external view returns (uint256);
/**
* @notice Gets the claimed reward amount for a specific address.
* @param user The address of the user.
* @return The amount of rewards claimed by the user.
*/
function claimed(address user) external view returns (uint256);
/**
* @notice Gets the address of the reward token.
* @return The address of the reward token.
*/
function token() external view returns (address);
/**
* @notice Gets the address of the minter contract.
* @return The address of the minter contract.
*/
function minter() external view returns (address);
/**
* @notice Gets the address of the voter contract.
* @return The address of the voter contract.
*/
function voter() external view returns (address);
/**
* @notice Gets the address of the authorized signer for reward claims.
* @return The address of the signer.
*/
function signer() external view returns (address);
/**
* @notice Gets the reward amount for a specific gauge and epoch.
* @param epoch The epoch for which to get the reward.
* @param gauge The address of the gauge.
* @return The reward amount for the specified gauge and epoch.
*/
function rewardPerGaugePerEpoch(uint256 epoch, address gauge) external view returns (uint256);
/**
* @notice Gets the total reward amount for a specific epoch.
* @param epoch The epoch for which to get the reward.
* @return The total reward amount for the specified epoch.
*/
function rewardPerEpoch(uint256 epoch) external view returns (uint256);
}
contracts/gauges/interfaces/IPerpetualsGauge.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
/**
* @title IPerpetualsGauge
* @dev Interface for the Perpetuals Gauge contract.
* This interface defines the events and functions for managing reward distribution.
*/
interface IPerpetualsGauge {
/**
* @dev Emitted when rewards are added.
* @param reward The amount of rewards added.
*/
event RewardAdded(uint256 reward);
/**
* @notice Returns the address of the reward token.
* @return The address of the reward token.
*/
function rewardToken() external view returns (address);
/**
* @notice Returns the address of the reward receiver contract.
* @return The address of the reward receiver contract.
*/
function rewarder() external view returns (address);
/**
* @notice Returns the address authorized to distribute rewards.
* @return The address authorized to distribute rewards.
*/
function DISTRIBUTION() external view returns (address);
/**
* @notice Returns the name of the gauge.
* @return The name of the gauge.
*/
function NAME() external view returns (string memory);
/**
* @notice Notifies the contract of the reward amount to be distributed.
* @param token_ The address of the reward token.
* @param rewardAmount_ The amount of reward tokens to be distributed.
*/
function notifyRewardAmount(address token_, uint256 rewardAmount_) external;
/**
* @notice Gets the reward for a specific account.
* @param user_ The address of the account to get the reward for.
*/
function getReward(address user_) external;
/**
* @notice Claims the fees for the internal_bribe.
* @return claimed0 The amount of the first token claimed.
* @return claimed1 The amount of the second token claimed.
*/
function claimFees() external returns (uint claimed0, uint claimed1);
}
@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165Upgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
function __ERC165_init() internal onlyInitializing {
}
function __ERC165_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165Upgradeable).interfaceId;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
contracts/core/interfaces/ICompoundEmissionExtension.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "./IVoter.sol";
/**
* @title ICompoundEmissionExtension
* @notice
* This interface defines the external functions and data structures for an extension
* that automatically compounds user emissions into veNFT locks and/or bribe pools.
* Users can configure how their claimed emissions are split across multiple veNFTs
* (`TargetLock[]`) and multiple bribe pools (`TargetPool[]`).
*/
interface ICompoundEmissionExtension {
/**
* @notice Describes a specific veNFT token lock target and the fraction of emissions to deposit there.
* @dev
* - `percentage` is in 1e18 format (1e18 = 100%).
* - If `tokenId` is zero, a new veNFT lock may be created in the compounding process.
*/
struct TargetLock {
/**
* @notice The identifier of an existing veNFT. If zero, a new veNFT may be created.
*/
uint256 tokenId;
/**
* @notice Fraction of allocated emissions for this target (1e18 = 100%).
*/
uint256 percentage;
}
/**
* @notice Defines the fraction of emissions to be sent to a particular pool's bribe contract.
* @dev
* - `percentage` is in 1e18 format (1e18 = 100%).
* - If the associated gauge is dead/killed, fallback logic may apply (e.g., lock creation).
*/
struct TargetPool {
/**
* @notice The address of the pool whose external bribe contract will receive emissions.
*/
address pool;
/**
* @notice Fraction of allocated emissions for this pool (1e18 = 100%).
*/
uint256 percentage;
}
/**
* @notice Encapsulates parameters for updating a user's compound-emission configuration in a single call.
* @dev
* - `toLocksPercentage + toBribePoolsPercentage` must not exceed 1e18.
* - Sum of percentages in `targetLocks` must be 1e18 if `toLocksPercentage > 0`.
* - Sum of percentages in `targetsBribePools` must be 1e18 if `toBribePoolsPercentage > 0`.
* - If updating `TargetLock[]` or `TargetPool[]`, the array must match the respective percentage being >0.
*/
struct UpdateCompoundEmissionConfigParams {
/**
* @notice Whether to update the overall fraction sent to locks vs. bribe pools.
*/
bool shouldUpdateGeneralPercentages;
/**
* @notice Whether to replace the user's entire array of `TargetLock[]`.
*/
bool shouldUpdateTargetLocks;
/**
* @notice Whether to replace the user's entire array of `TargetPool[]`.
*/
bool shouldUpdateTargetBribePools;
/**
* @notice Fraction of the user's total emissions allocated to veNFT locks (1e18 = 100%).
*/
uint256 toLocksPercentage;
/**
* @notice Fraction of the user's total emissions allocated to bribe pools (1e18 = 100%).
*/
uint256 toBribePoolsPercentage;
/**
* @notice The new set of veNFT lock targets (replaces the old array if updated).
*/
TargetLock[] targetLocks;
/**
* @notice The new set of bribe pool targets (replaces the old array if updated).
*/
TargetPool[] targetsBribePools;
}
/**
* @notice Configuration options for creating or depositing into veNFTs during compounding.
* @dev
* - If `withPermanentLock` is `true`, `lockDuration` is ignored (the lock is permanent).
* - If `managedTokenIdForAttach` is nonzero, the deposit may be attached to an existing managed veNFT.
*/
struct CreateLockConfig {
/**
* @notice Whether the created lock should be considered "boosted" (if the underlying system supports boosted logic).
*/
bool shouldBoosted;
/**
* @notice Whether the lock is permanent (no withdrawal).
*/
bool withPermanentLock;
/**
* @notice Duration in seconds for the lock if it is not permanent.
*/
uint256 lockDuration;
/**
* @notice An optional managed veNFT ID for attaching a new deposit.
*/
uint256 managedTokenIdForAttach;
}
/**
* @notice Parameters to claim emissions from the Voter, which are then compounded into locks and/or bribe pools.
* @dev
* - `target` is the user whose emissions are being claimed.
* - `gauges` is a list of gauge addresses for which to claim the user’s emissions.
* - `blaze` is optional blaze-based claim data (if the Voter supports blaze signature).
*/
struct ClaimParams {
/**
* @notice The user whose emissions will be claimed and compounded.
*/
address target;
/**
* @notice The gauge addresses to claim emissions from.
*/
address[] gauges;
/**
* @notice Optional data for blaze-based claims, if applicable in the Voter implementation.
*/
IVoter.AggregateClaimBlazeDataParams blaze;
}
// --------------------- Events ---------------------
/**
* @notice Emitted when the default lock configuration is updated.
* @param config The new default configuration for creating veNFT locks.
*/
event SetDefaultCreateLockConfig(CreateLockConfig config);
/**
* @notice Emitted when a user sets or removes their custom configuration for creating veNFT locks.
* @param user The user whose configuration changed.
* @param config The new config, or default values if removed.
*/
event SetCreateLockConfig(address indexed user, CreateLockConfig config);
/**
* @notice Emitted when a user updates their overall percentages of emissions allocated to locks vs. bribe pools.
* @param user The user whose allocation changed.
* @param toLocksPercentage Fraction of emissions allocated to locks (1e18 = 100%).
* @param toBribePoolsPercentage Fraction of emissions allocated to bribe pools (1e18 = 100%).
*/
event SetCompoundEmissionGeneralPercentages(address indexed user, uint256 toLocksPercentage, uint256 toBribePoolsPercentage);
/**
* @notice Emitted when a user replaces or updates their entire array of lock targets.
* @param user The user whose targets changed.
* @param targetLocks The new array of `TargetLock` structs.
*/
event SetCompoundEmissionTargetLocks(address indexed user, TargetLock[] targetLocks);
/**
* @notice Emitted when a user replaces or updates their entire array of bribe pool targets.
* @param user The user whose targets changed.
* @param targetBribePools The new array of `TargetPool` structs.
*/
event SetCompoundEmissionTargetBribePools(address indexed user, TargetPool[] targetBribePools);
/**
* @notice Emitted when a user changes the veNFT token ID in an existing emission distribution target lock.
* @param user The user making the change.
* @param targetLockFromId The old token ID to be replaced.
* @param targetTokenToId The new token ID (0 if effectively removing the old lock reference).
*/
event ChangeEmissionTargetLock(address indexed user, uint256 targetLockFromId, uint256 targetTokenToId);
/**
* @notice Emitted when a new veNFT lock is created due to emission compounding.
* @param user The user for whom the lock was created.
* @param tokenId The newly created veNFT token ID.
* @param amount The amount of tokens locked.
*/
event CreateLockFromCompoundEmission(address indexed user, uint256 indexed tokenId, uint256 amount);
/**
* @notice Emitted when a new veNFT lock is created during fallback logic for bribe pools (e.g., if the gauge is killed).
* @param user The user for whom the fallback lock was created.
* @param pool The bribe pool that was originally intended to receive tokens.
* @param tokenId The newly created veNFT token ID.
* @param amount The amount of tokens locked instead of bribe distribution.
*/
event CreateLockFromCompoundEmissionForBribePools(address indexed user, address pool, uint256 indexed tokenId, uint256 amount);
/**
* @notice Emitted when a user compounds emissions into a specific bribe pool.
* @param user The user compounding emissions.
* @param pool The bribe pool receiving the tokens.
* @param amount The amount of tokens deposited.
*/
event CompoundEmissionToBribePool(address indexed user, address pool, uint256 amount);
/**
* @notice Emitted when a user compounds emissions into an existing veNFT lock.
* @param user The address of the user compounding emissions.
* @param tokenId The identifier of the veNFT receiving the deposit.
* @param amount The amount of tokens deposited into the lock.
*/
event CompoundEmissionToTargetLock(address indexed user, uint256 indexed tokenId, uint256 amount);
// --------------------- External Functions ---------------------
/**
* @notice Batch operation to claim and compound emissions for multiple users simultaneously.
* @dev
* - Only callable by addresses with the COMPOUND_KEEPER_ROLE.
* - Iterates over each user's claim, collecting and distributing emissions according to user configs.
*
* @param claimsParams_ An array of `ClaimParams`, one for each user to process.
*/
function compoundEmissionClaimBatch(ClaimParams[] calldata claimsParams_) external;
/**
* @notice Allows an individual user to claim and compound emissions for specified gauges.
* @dev
* - Only callable by the user matching `claimParams_.target`.
* - Collects emissions from the specified gauges, then distributes them to veNFT locks and bribe pools.
*
* @param claimParams_ The struct containing:
* - `target`: user whose emissions are being claimed.
* - `gauges`: gauges to claim from.
* - `merkl`: optional merkle claim data.
*/
function compoundEmisisonClaim(ClaimParams calldata claimParams_) external;
/**
* @notice Updates occurrences of `targetTokenId_` in a user's `TargetLock[]` to `newTokenId_`.
* @dev
* - If multiple entries reference `targetTokenId_`, all will be replaced.
* - If `newTokenId_ = 0`, references to `targetTokenId_` are cleared,
* effectively enabling a new veNFT to be created in future compounding for that portion.
* - Typically called by the Voter after a veNFT transfer or merge.
*
* @param target_ The user whose `TargetLock[]` will be updated.
* @param targetTokenId_ The old token ID to search for in that user's target array.
* @param newTokenId_ The new token ID to replace the old one. Zero if removing.
*/
function changeEmissionTargetLockId(address target_, uint256 targetTokenId_, uint256 newTokenId_) external;
/**
* @notice Retrieves the fraction (in 1e18 scale) of a user's emissions allocated to veNFT locks.
* @dev A value of 1e18 indicates 100%. If this returns 5e17, that means 50% is allocated.
* @param target_ The user address to query.
* @return The fraction of emissions (1e18 = 100%) allocated to locks.
*/
function getToLocksPercentage(address target_) external view returns (uint256);
/**
* @notice Returns how much of a given `amountIn_` would be allocated to locks vs. bribe pools for a user.
* @dev Does not break down per veNFT or per pool, only the high-level split.
* @param target_ The user whose configuration to apply.
* @param amountIn_ The total emission amount to be distributed.
* @return toTargetLocks The portion allocated to veNFT locks.
* @return toTargetBribePools The portion allocated to bribe pools.
*/
function getAmountOutToCompound(
address target_,
uint256 amountIn_
) external view returns (uint256 toTargetLocks, uint256 toTargetBribePools);
}
contracts/mocks/ICHIMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IPairIntegrationInfo} from "../integration/interfaces/IPairIntegrationInfo.sol";
contract ICHIMock is ERC20 {
address public pool;
uint8 internal _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
_decimals = decimals_;
}
function setPool(address pool_) external {
pool = pool_;
}
function token0() external returns (address) {
return IPairIntegrationInfo(pool).token0();
}
function token1() external returns (address) {
return IPairIntegrationInfo(pool).token1();
}
}
@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol)
pragma solidity ^0.8.0;
import "../ERC721.sol";
import "../extensions/ERC721Enumerable.sol";
import "../extensions/ERC721Burnable.sol";
import "../extensions/ERC721Pausable.sol";
import "../../../access/AccessControlEnumerable.sol";
import "../../../utils/Context.sol";
import "../../../utils/Counters.sol";
/**
* @dev {ERC721} token, including:
*
* - ability for holders to burn (destroy) their tokens
* - a minter role that allows for token minting (creation)
* - a pauser role that allows to stop all token transfers
* - token ID and URI autogeneration
*
* This contract uses {AccessControl} to lock permissioned functions using the
* different roles - head to its documentation for details.
*
* The account that deploys the contract will be granted the minter and pauser
* roles, as well as the default admin role, which will let it grant both minter
* and pauser roles to other accounts.
*
* _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._
*/
contract ERC721PresetMinterPauserAutoId is
Context,
AccessControlEnumerable,
ERC721Enumerable,
ERC721Burnable,
ERC721Pausable
{
using Counters for Counters.Counter;
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
Counters.Counter private _tokenIdTracker;
string private _baseTokenURI;
/**
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
* account that deploys the contract.
*
* Token URIs will be autogenerated based on `baseURI` and their token IDs.
* See {ERC721-tokenURI}.
*/
constructor(string memory name, string memory symbol, string memory baseTokenURI) ERC721(name, symbol) {
_baseTokenURI = baseTokenURI;
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender());
_setupRole(PAUSER_ROLE, _msgSender());
}
function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
}
/**
* @dev Creates a new token for `to`. Its token ID will be automatically
* assigned (and available on the emitted {IERC721-Transfer} event), and the token
* URI autogenerated based on the base URI passed at construction.
*
* See {ERC721-_mint}.
*
* Requirements:
*
* - the caller must have the `MINTER_ROLE`.
*/
function mint(address to) public virtual {
require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint");
// We cannot just use balanceOf to create the new tokenId because tokens
// can be burned (destroyed), so we need a separate counter.
_mint(to, _tokenIdTracker.current());
_tokenIdTracker.increment();
}
/**
* @dev Pauses all token transfers.
*
* See {ERC721Pausable} and {Pausable-_pause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function pause() public virtual {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to pause");
_pause();
}
/**
* @dev Unpauses all token transfers.
*
* See {ERC721Pausable} and {Pausable-_unpause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function unpause() public virtual {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to unpause");
_unpause();
}
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override(ERC721, ERC721Enumerable, ERC721Pausable) {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(AccessControlEnumerable, ERC721, ERC721Enumerable) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
contracts/integration/UpgradeCall.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {IUpgradeCall} from "./interfaces/IUgradeCall.sol";
abstract contract UpgradeCall is IUpgradeCall {
function upgradeCall() external virtual override {}
}
@openzeppelin/contracts/token/ERC721/IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
contracts/core/interfaces/IVeLuteSplitMerklAidrop.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
/**
* @title IVeLuteSplitMerklAidrop
* @dev Interface for the VeLuteSplitMerklAidropUpgradeable contract.
*/
interface IVeLuteSplitMerklAidrop {
/**
* @dev Emitted when a user claims their tokens.
* @param user The address of the user.
* @param claimAmount The total amount of tokens claimed.
* @param toTokenAmount The amount of tokens transferred directly to the user.
* @param toVeNFTAmount The amount of tokens locked as veNFT.
* @param tokenId The ID of the veNFT lock created.
*/
event Claim(address indexed user, uint256 claimAmount, uint256 toTokenAmount, uint256 toVeNFTAmount, uint256 tokenId);
/**
* @dev Emitted when the Merkle root is set.
* @param merklRoot The new Merkle root.
*/
event SetMerklRoot(bytes32 merklRoot);
/**
* @dev Emitted when the pure tokens rate is set.
* @param pureTokensRate The new pure tokens rate.
*/
event SetPureTokensRate(uint256 pureTokensRate);
/**
* @dev Emitted when the allowed status of a claim operator is set or changed.
* @param operator The address of the claim operator.
* @param isAllowed A boolean indicating whether the operator is allowed.
*/
event SetIsAllowedClaimOperator(address indexed operator, bool indexed isAllowed);
/**
* @dev Emitted when tokens are recovered from the contract.
* @param sender address that performed the recovery.
* @param amount of tokens recovered.
*/
event Recover(address indexed sender, uint256 amount);
/**
* @dev Allows a user to claim tokens or veNFT tokens based on a Merkle proof.
* @param inPureTokens_ Boolean indicating if the claim is in pure tokens.
* @param amount_ The amount to claim.
* @param withPermanentLock_ Whether the lock should be permanent.
* @param managedTokenIdForAttach_ The ID of the managed NFT to attach, if any. 0 for ignore
* @param proof_ The Merkle proof for the claim.
* @notice This function can only be called when the contract is not paused.
*/
function claim(
bool inPureTokens_,
uint256 amount_,
bool withPermanentLock_,
uint256 managedTokenIdForAttach_,
bytes32[] memory proof_
) external;
/**
* @dev Allows a claim operator to claim tokens on behalf of a target address.
* @param target_ The address of the user on whose behalf tokens are being claimed.
* @param inPureTokens_ Boolean indicating if the claim is in pure tokens.
* @param amount_ The amount to claim.
* @param withPermanentLock_ Whether the lock should be permanent.
* @param managedTokenIdForAttach_ The ID of the managed NFT to attach, if any. 0 for ignore
* @param proof_ The Merkle proof verifying the user's claim.
* @notice This function can only be called when the contract is not paused.
* @notice Reverts with `NotAllowedClaimOperator` if the caller is not an allowed claim operator.
* @notice Emits a {Claim} event.
*/
function claimFor(
address target_,
bool inPureTokens_,
uint256 amount_,
bool withPermanentLock_,
uint256 managedTokenIdForAttach_,
bytes32[] memory proof_
) external;
/**
* @dev Sets whether an address is allowed to operate claims on behalf of others.
* Can only be called by the owner.
* @param operator_ The address of the operator to set.
* @param isAllowed_ A boolean indicating whether the operator is allowed.
* @notice Emits a {SetIsAllowedClaimOperator} event.
*/
function setIsAllowedClaimOperator(address operator_, bool isAllowed_) external;
/**
* @dev Pauses the contract, preventing any further claims.
* Can only be called by the owner.
*/
function pause() external;
/**
* @dev Unpauses the contract, allowing claims to be made.
* Can only be called by the owner.
*/
function unpause() external;
/**
* @dev Sets the Merkle root for verifying claims.
* Can only be called by the owner when the contract is paused.
* @param merklRoot_ The new Merkle root.
*/
function setMerklRoot(bytes32 merklRoot_) external;
/**
* @dev Sets the pure tokens rate.
* Can only be called by the owner when the contract is paused.
* @param pureTokensRate_ The new pure tokens rate.
* @notice Emits a {SetPureTokensRate} event.
*/
function setPureTokensRate(uint256 pureTokensRate_) external;
/**
* @notice Allows the owner to recover tokens from the contract.
* @param amount_ The amount of tokens to be recovered.
* Transfers the specified amount of tokens to the owner's address.
*/
function recoverToken(uint256 amount_) external;
/**
* @dev Verifies if a provided proof is valid for a given user and amount.
* @param user_ The address of the user.
* @param amount_ The amount to be verified.
* @param proof_ The Merkle proof.
* @return True if the proof is valid, false otherwise.
*/
function isValidProof(address user_, uint256 amount_, bytes32[] memory proof_) external view returns (bool);
/**
* @dev Returns the address of the token contract.
*/
function token() external view returns (address);
/**
* @dev Returns the address of the Voting Escrow contract.
*/
function votingEscrow() external view returns (address);
/**
* @dev Rate for pure tokens.
*/
function pureTokensRate() external view returns (uint256);
/**
* @dev Returns the Merkle root used for verifying claims.
*/
function merklRoot() external view returns (bytes32);
/**
* @dev Returns the amount of tokens claimed by a user.
* @param user The address of the user.
* @return The amount of tokens claimed by the user.
*/
function userClaimed(address user) external view returns (uint256);
/**
* @dev Checks if an address is an allowed claim operator.
* @param operator_ The address to check.
* @return true if the operator is allowed, false otherwise.
*/
function isAllowedClaimOperator(address operator_) external view returns (bool);
/**
* @dev Calculates the equivalent amount in pure tokens based on the claim amount.
* @param claimAmount_ The claim amount for which to calculate the equivalent pure tokens.
* @return The calculated amount of pure tokens.
*/
function calculatePureTokensAmount(uint256 claimAmount_) external view returns (uint256);
}
contracts/core/interfaces/IVault.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
interface IVault {
function pool() external view returns (address);
}
@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/utils/ERC721Holder.sol)
pragma solidity ^0.8.0;
import "../IERC721ReceiverUpgradeable.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
*/
contract ERC721HolderUpgradeable is Initializable, IERC721ReceiverUpgradeable {
function __ERC721Holder_init() internal onlyInitializing {
}
function __ERC721Holder_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/fees/FeesVaultFactoryUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {IFeesVaultFactory} from "./interfaces/IFeesVaultFactory.sol";
import {IFeesVault} from "./interfaces/IFeesVault.sol";
import {IPairIntegrationInfo} from "../integration/interfaces/IPairIntegrationInfo.sol";
import {FeesVaultProxy} from "./FeesVaultProxy.sol";
contract FeesVaultFactoryUpgradeable is IFeesVaultFactory, AccessControlUpgradeable {
bytes32 public constant CLAIM_FEES_CALLER_ROLE = keccak256("CLAIM_FEES_CALLER_ROLE");
bytes32 public constant WHITELISTED_CREATOR_ROLE = keccak256("WHITELISTED_CREATOR_ROLE");
bytes32 public constant FEES_VAULT_ADMINISTRATOR_ROLE = keccak256("FEES_VAULT_ADMINISTRATOR_ROLE");
address public override feesVaultImplementation;
address public override voter;
mapping(address => address) public override getVaultForPool;
mapping(address => bool) public override isCustomConfig;
DistributionConfig internal _defaultDistributionConfig;
mapping(address => DistributionConfig) internal _customDistributionConfigs;
mapping(address creator => DistributionConfig) internal _creatorDistributionConfigs;
mapping(address feesVault => address creator) internal _feesVaultCreator;
error AddressZero();
/**
* @dev Constructor that disables initialization on implementation.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the factory with necessary parameters and default configurations.
* @param voter_ The default voter address for fee vaults.
* @param feesVaultImplementation_ The default fees vault implementation address.
* @param defaultDistributionConfig_ The default distribution configuration for fees.
*/
function initialize(
address voter_,
address feesVaultImplementation_,
DistributionConfig memory defaultDistributionConfig_
) external initializer {
_checkAddressZero(voter_);
_checkAddressZero(feesVaultImplementation_);
_checkDistributionConfig(defaultDistributionConfig_);
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
voter = voter_;
feesVaultImplementation = feesVaultImplementation_;
_defaultDistributionConfig = defaultDistributionConfig_;
}
/**
* @notice Changes the implementation of the fees vault used by all vaults.
* @param implementation_ The new fees vault implementation address.
*/
function changeImplementation(address implementation_) external onlyRole(DEFAULT_ADMIN_ROLE) {
_checkAddressZero(implementation_);
emit FeesVaultImplementationChanged(feesVaultImplementation, implementation_);
feesVaultImplementation = implementation_;
}
/**
* @dev Sets the address used for voting in the fee vaults. Only callable by the contract owner.
*
* @param voter_ The new voter address to be set.
*/
function setVoter(address voter_) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_checkAddressZero(voter_);
emit SetVoter(voter, voter_);
voter = voter_;
}
/**
* @notice Sets a default distribution configuration for a fees vaults.
* @param config_ The distribution configuration to apply.
*/
function setDefaultDistributionConfig(
DistributionConfig memory config_
) external virtual override onlyRole(FEES_VAULT_ADMINISTRATOR_ROLE) {
_checkDistributionConfig(config_);
_defaultDistributionConfig = config_;
emit DefaultDistributionConfig(config_);
}
/**
* @notice Sets a custom distribution configuration for a specific fees vault.
* @param feesVault_ The address of the fees vault to configure.
* @param config_ The custom distribution configuration to apply.
*/
function setCustomDistributionConfig(
address feesVault_,
DistributionConfig memory config_
) external virtual override onlyRole(FEES_VAULT_ADMINISTRATOR_ROLE) {
if (config_.toGaugeRate == 0 && config_.recipients.length == 0 && config_.rates.length == 0) {
delete _customDistributionConfigs[feesVault_];
delete isCustomConfig[feesVault_];
} else {
_checkDistributionConfig(config_);
isCustomConfig[feesVault_] = true;
_customDistributionConfigs[feesVault_] = config_;
}
emit CustomDistributionConfig(feesVault_, config_);
}
/**
* @notice Changes the creator for multiple fees vaults.
* @param creator_ The new creator address.
* @param feesVaults_ The array of fees vault addresses.
*/
function changeCreatorForFeesVaults(
address creator_,
address[] calldata feesVaults_
) external virtual override onlyRole(DEFAULT_ADMIN_ROLE) {
for (uint256 i; i < feesVaults_.length; ) {
_feesVaultCreator[feesVaults_[i]] = creator_;
unchecked {
i++;
}
}
emit ChangeCreatorForFeesVaults(creator_, feesVaults_);
}
/**
* @notice Sets a distribution configuration for a specific creator.
* @param creator_ The address of the creator of fees vaults.
* @param config_ The distribution configuration to apply.
*/
function setDistributionConfigForCreator(
address creator_,
DistributionConfig memory config_
) external virtual override onlyRole(FEES_VAULT_ADMINISTRATOR_ROLE) {
if (config_.toGaugeRate == 0 && config_.recipients.length == 0 && config_.rates.length == 0) {
delete _creatorDistributionConfigs[creator_];
} else {
_checkDistributionConfig(config_);
_creatorDistributionConfigs[creator_] = config_;
}
emit CreatorDistributionConfig(creator_, config_);
}
/**
* @dev Creates a new fee vault for a given pool if it hasn't been created yet. Only callable by whitelisted creators.
*
* @param pool_ The address of the pool for which the fee vault is to be created.
* @return The address of the newly created fee vault.
*/
function createVaultForPool(address pool_) external virtual override onlyRole(WHITELISTED_CREATOR_ROLE) returns (address) {
if (getVaultForPool[pool_] != address(0)) {
revert AlreadyCreated();
}
address newFeesVault = address(new FeesVaultProxy());
IFeesVault(newFeesVault).initialize(address(this), pool_);
getVaultForPool[pool_] = newFeesVault;
_feesVaultCreator[newFeesVault] = _msgSender();
emit FeesVaultCreated(pool_, newFeesVault);
return newFeesVault;
}
/**
* @notice Retrieves the distribution configuration for a specific fees vault.
* @param feesVault_ The address of the fees vault.
* @return toGaugeRate The rate at which fees are distributed to the gauge.
* @return recipients The addresses of the recipients.
* @return rates The rates at which fees are distributed to the recipients.
*/
function getDistributionConfig(
address feesVault_
) external view virtual override returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates) {
DistributionConfig memory config;
if (isCustomConfig[feesVault_]) {
config = _customDistributionConfigs[feesVault_];
} else {
address creator = _feesVaultCreator[feesVault_];
DistributionConfig memory creatorConfig = _creatorDistributionConfigs[creator];
if (creator != address(0) && (creatorConfig.toGaugeRate > 0 || creatorConfig.recipients.length > 0)) {
config = creatorConfig;
} else {
config = _defaultDistributionConfig;
}
}
return (config.toGaugeRate, config.recipients, config.rates);
}
/**
* @notice Retrieves the creator address for a specific fees vault.
* @param feesVault_ The address of the fees vault.
* @return The address of the creator associated with the specified fees vault.
*/
function getFeesVaultCreator(address feesVault_) external view returns (address) {
return _feesVaultCreator[feesVault_];
}
/**
* @notice Retrieves the distribution configuration for a specific creator.
* @param creator_ The address of the creator.
* @return toGaugeRate The rate at which fees are distributed to the gauge.
* @return recipients The addresses of the recipients.
* @return rates The rates at which fees are distributed to the recipients.
*/
function creatorDistributionConfig(
address creator_
) external view virtual override returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates) {
DistributionConfig memory config = _creatorDistributionConfigs[creator_];
return (config.toGaugeRate, config.recipients, config.rates);
}
/**
* @notice Returns the default distribution configuration used by the factory.
* @return toGaugeRate The default rate at which fees are distributed to the gauge.
* @return recipients The default addresses of the recipients.
* @return rates The default rates at which fees are distributed to the recipients.
*/
function defaultDistributionConfig()
external
view
virtual
override
returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates)
{
DistributionConfig memory config = _defaultDistributionConfig;
return (config.toGaugeRate, config.recipients, config.rates);
}
/**
* @notice Returns the custom distribution configuration for a specified fees vault.
* @param feesVault_ The address of the fees vault.
* @return toGaugeRate The rate at which fees are distributed to the gauge.
* @return recipients The addresses of the recipients.
* @return rates The rates at which fees are distributed to the recipients.
*/
function customDistributionConfig(
address feesVault_
) external view virtual override returns (uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates) {
DistributionConfig memory config = _customDistributionConfigs[feesVault_];
return (config.toGaugeRate, config.recipients, config.rates);
}
/**
* @dev Internal function to check distribution configurations for validity.
* @param config_ The distribution configuration to check.
*/
function _checkDistributionConfig(DistributionConfig memory config_) internal pure virtual {
uint256 totalSums = config_.toGaugeRate;
if (config_.rates.length != config_.recipients.length) {
revert ArraysLengthMismatch();
}
for (uint256 i; i < config_.recipients.length; ) {
_checkAddressZero(config_.recipients[i]);
if (config_.rates[i] == 0) {
revert IncorrectRates();
}
totalSums += config_.rates[i];
unchecked {
i++;
}
}
if (totalSums != 10000) {
revert IncorrectRates();
}
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[41] private __gap;
}
@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControlEnumerableUpgradeable.sol";
import "./AccessControlUpgradeable.sol";
import "../utils/structs/EnumerableSetUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerableUpgradeable, AccessControlUpgradeable {
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
mapping(bytes32 => EnumerableSetUpgradeable.AddressSet) private _roleMembers;
function __AccessControlEnumerable_init() internal onlyInitializing {
}
function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerableUpgradeable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_roleMembers[role].add(account);
}
/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
super._revokeRole(role, account);
_roleMembers[role].remove(account);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
contracts/gauges/interfaces/IRewarder.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
interface IRewarder {
function onReward(address user, address recipient, uint256 userBalance) external;
}
contracts/fees/FeesVaultUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IPairIntegrationInfo} from "../integration/interfaces/IPairIntegrationInfo.sol";
import {IVoter} from "../core/interfaces/IVoter.sol";
import {IFeesVault} from "./interfaces/IFeesVault.sol";
import {IFeesVaultFactory} from "./interfaces/IFeesVaultFactory.sol";
import {UpgradeCall} from "../integration/UpgradeCall.sol";
/**
* @title Fees Vault Factory
* @dev Factory contract for creating and managing fees vault instances.
* Implements access control.
*/
contract FeesVaultUpgradeable is IFeesVault, Initializable, UpgradeCall {
using SafeERC20 for IERC20;
uint256 internal constant _PRECISION = 10000; // 100%
address public override factory;
address public override pool;
error AddressZero();
/**
* @dev Constructor that disables initialization on implementation.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract with necessary configuration.
* @param factory_ Factory address for this vault.
* @param pool_ Address of the liquidity pool.
*/
function initialize(address factory_, address pool_) external virtual override initializer {
if (factory_ == address(0) || pool_ == address(0)) {
revert AddressZero();
}
factory = factory_;
pool = pool_;
}
/**
* @notice Claims fees for redistribution.
* @return gauge0 Amount of fees distributed to the gauge for token0.
* @return gauge1 Amount of fees distributed to the gauge for token1.
*/
function claimFees() external virtual override returns (uint256, uint256) {
IFeesVaultFactory factoryCache = IFeesVaultFactory(factory);
(uint256 toGaugeRate, address[] memory recipients, uint256[] memory rates_) = factoryCache.getDistributionConfig(address(this));
address poolCache = pool;
if (toGaugeRate > 0) {
address voterCache = IFeesVaultFactory(factory).voter();
if (!IVoter(voterCache).isGauge(msg.sender)) {
revert AccessDenied();
}
if (poolCache != IVoter(voterCache).poolForGauge(msg.sender)) {
revert PoolMismatch();
}
} else {
if (!factoryCache.hasRole(factoryCache.CLAIM_FEES_CALLER_ROLE(), msg.sender)) {
revert AccessDenied();
}
}
(address token0, address token1) = (IPairIntegrationInfo(poolCache).token0(), IPairIntegrationInfo(poolCache).token1());
(uint256 gauge0, uint256 totalAmount0) = _distributeFees(token0, toGaugeRate, recipients, rates_);
(uint256 gauge1, uint256 totalAmount1) = _distributeFees(token1, toGaugeRate, recipients, rates_);
emit Fees(poolCache, token0, token1, totalAmount0, totalAmount1);
return (gauge0, gauge1);
}
/**
* @notice Allows for the emergency recovery of ERC20 tokens from
* caller with FEES_VAULT_ADMINISTRATOR_ROLE .
* @param token_ The token to recover.
* @param amount_ The amount to recover.
*/
function emergencyRecoverERC20(address token_, uint256 amount_) external virtual override {
IFeesVaultFactory factoryCache = IFeesVaultFactory(factory);
if (!factoryCache.hasRole(factoryCache.FEES_VAULT_ADMINISTRATOR_ROLE(), msg.sender)) {
revert AccessDenied();
}
IERC20(token_).safeTransfer(msg.sender, amount_);
}
/**
* @dev Internal function to distribute fees to various recipients.
* @param tokenAddress_ The address of the token to distribute.
* @param toGaugeRate_ The rate at which fees are distributed to the gauge.
* @param recipients_ The recipients of the fees.
* @param rates_ The rates at which fees are distributed to the recipients.
* @return toGaugeAmount The amount distributed to the gauge.
* @return totalAmount The total amount distributed.
*/
function _distributeFees(
address tokenAddress_,
uint256 toGaugeRate_,
address[] memory recipients_,
uint256[] memory rates_
) internal virtual returns (uint256 toGaugeAmount, uint256 totalAmount) {
IERC20 token = IERC20(tokenAddress_);
totalAmount = token.balanceOf(address(this));
toGaugeAmount = (toGaugeRate_ * totalAmount) / _PRECISION;
if (toGaugeAmount > 0) {
token.safeTransfer(msg.sender, toGaugeAmount);
emit FeesToGauge(tokenAddress_, msg.sender, toGaugeAmount);
}
for (uint256 i; i < recipients_.length; ) {
uint256 toRecipient = ((rates_[i]) * totalAmount) / _PRECISION;
if (toRecipient > 0) {
token.safeTransfer(recipients_[i], toRecipient);
emit FeesToOtherRecipient(tokenAddress_, recipients_[i], toRecipient);
}
unchecked {
i++;
}
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/gauges/interfaces/IPerpetualsTradersRewarder.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {IRewardReciever} from "./IRewardReciever.sol";
/**
* @title IPerpetualsTradersRewarder
* @dev Interface for rewarding perpetual traders. This interface extends the IRewardReciever
* interface and adds functionalities specific to perpetual traders rewards.
*/
interface IPerpetualsTradersRewarder is IRewardReciever {
/**
* @dev Emitted when the signer address is set.
* @param signer The address of the new signer.
*/
event SetSigner(address indexed signer);
/**
* @dev Emitted when a user claims their reward.
* @param user The address of the user making the claim.
* @param timestamp The timestamp when the claim was made.
* @param amount The amount of tokens claimed.
*/
event Claim(address indexed user, uint256 indexed timestamp, uint256 indexed amount);
/**
* @dev Emitted when a reward is notified.
* @param caller The address of the caller notifying the reward.
* @param timestamp The timestamp when the reward was notified.
* @param amount The amount of tokens notified.
*/
event Reward(address indexed caller, uint256 indexed timestamp, uint256 amount);
/**
* @notice Sets the signer address.
* @param signer_ The address of the new signer.
*/
function setSigner(address signer_) external;
/**
* @notice Claims the reward for the user.
* @param amount_ The amount of tokens to claim.
* @param signature_ The signature of the claim.
* @return reward The amount of reward tokens claimed.
*/
function claim(uint256 amount_, bytes memory signature_) external returns (uint256 reward);
/**
* @notice Returns the address of the gauge.
* @return The address of the gauge.
*/
function gauge() external view returns (address);
/**
* @notice Returns the address of the signer.
* @return The address of the signer.
*/
function signer() external view returns (address);
/**
* @notice Returns the address of the reward token.
* @return The address of the reward token.
*/
function token() external view returns (address);
/**
* @notice Returns the amount of tokens total reward
* @return The amount of notified tokens reward.
*/
function totalReward() external view returns (uint256);
/**
* @notice Returns the amount of tokens claimed by a user.
* @param user_ The address of the user.
* @return The amount of tokens claimed by the user.
*/
function claimed(address user_) external view returns (uint256);
/**
* @notice Returns the total amount of tokens claimed.
* @return The total amount of tokens claimed.
*/
function totalClaimed() external view returns (uint256);
}
contracts/core/interfaces/IVeArtProxyStatic.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IVeArtProxyStatic {
function endPart() external view returns (string memory);
function startPart() external view returns (string memory);
}
contracts/integration/interfaces/IMerklDistributor.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IMerklDistributor {
function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) external;
}
@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20PermitUpgradeable {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
function __Ownable2Step_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable2Step_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
contracts/fees/interfaces/IFeesVault.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title Fees Vault Interface
* @dev Interface for the FeesVault contract responsible for managing fee distribution.
* Defines the essential functions and events for fee claiming and configuration.
*/
interface IFeesVault {
/**
* @dev Emitted when fees are claimed from the gauge and distributed.
* @param pool Address of the liquidity pool.
* @param token0 Address of the first token in the pool.
* @param token1 Address of the second token in the pool.
* @param totalAmount0 Total amount of token0 distributed.
* @param totalAmount1 Total amount of token1 distributed.
*/
event Fees(address indexed pool, address indexed token0, address indexed token1, uint256 totalAmount0, uint256 totalAmount1);
/**
* @notice Emitted when fees are distributed to the gauge.
* @param token Address of the token distributed.
* @param recipient Address of the gauge receiving the fees.
* @param amount Amount of fees distributed.
*/
event FeesToGauge(address indexed token, address indexed recipient, uint256 amount);
/**
* @notice Emitted when fees are distributed to a recipient other than the gauge.
* @param token Address of the token distributed.
* @param recipient Address of the entity receiving the fees.
* @param amount Amount of fees distributed.
*/
event FeesToOtherRecipient(address indexed token, address indexed recipient, uint256 amount);
/**
* @dev Reverts if the caller is not authorized to perform the operation.
*/
error AccessDenied();
/**
* @dev Reverts if the pool address provided does not match the pool address stored for a gauge.
*/
error PoolMismatch();
/**
* @notice Gets the factory address associated with this fees vault.
* @return The address of the factory contract.
*/
function factory() external view returns (address);
/**
* @notice Gets the pool address associated with this fees vault.
* @return The address of the liquidity pool.
*/
function pool() external view returns (address);
/**
* @notice Claims accumulated fees for the calling gauge and distributes them according to configured rates.
* @dev Can only be called by an authorized gauge. Distributes fees in both tokens of the associated pool.
* @return gauge0 Amount of token0 distributed to the calling gauge.
* @return gauge1 Amount of token1 distributed to the calling gauge.
*/
function claimFees() external returns (uint256 gauge0, uint256 gauge1);
/**
* @notice Allows the contract owner to recover ERC20 tokens accidentally sent to this contract.
* @param token_ The ERC20 token address to recover.
* @param amount_ The amount of tokens to recover.
*/
function emergencyRecoverERC20(address token_, uint256 amount_) external;
/**
* @dev Initializes the contract with necessary configuration parameters.
* Can only be called once by the contract factory during the deployment process.
* @param factory_ Address of the contract factory for this vault.
* @param pool_ Address of the liquidity pool associated with this vault.
*/
function initialize(address factory_, address pool_) external;
}
contracts/dexV2/interfaces/IPair.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IPair {
function setCommunityVault(address communityVault_) external;
function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1);
function claimFees() external returns (uint, uint);
function tokens() external view returns (address, address);
function token0() external view returns (address);
function token1() external view returns (address);
function transferFrom(address src, address dst, uint amount) external returns (bool);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function burn(address to) external returns (uint amount0, uint amount1);
function mint(address to) external returns (uint liquidity);
function getReserves() external view returns (uint _reserve0, uint _reserve1, uint _blockTimestampLast);
function getAmountOut(uint, address) external view returns (uint);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint);
function decimals() external view returns (uint8);
function claimable0(address _user) external view returns (uint);
function claimable1(address _user) external view returns (uint);
function isStable() external view returns (bool);
function initialize(
address token0,
address token1,
bool isStable,
address communityVault
) external;
function fees() external view returns (address);
}
contracts/lute/ManagedNFTManagerUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {IVotingEscrow} from "../core/interfaces/IVotingEscrow.sol";
import {IManagedNFTStrategy} from "./interfaces/IManagedNFTStrategy.sol";
import {IManagedNFTManager} from "./interfaces/IManagedNFTManager.sol";
/**
* @title Managed NFT Manager Upgradeable
* @dev Manages the lifecycle and access control for NFTs used in a managed strategy, leveraging governance and escrow functionalities.
* This contract serves as the central point for managing NFTs, their attachments to strategies, and authorized user interactions.
*/
contract ManagedNFTManagerUpgradeable is IManagedNFTManager, AccessControlUpgradeable {
/**
* @dev Error indicating an unauthorized access attempt.
*/
error AccessDenied();
/**
* @dev Error indicating an operation attempted on a managed NFT that is currently disabled.
*/
error ManagedNFTIsDisabled();
/**
* @dev Error indicating that a required attachment action was not found or is missing.
*/
error NotAttached();
/**
* @dev Error indicating that the specified token ID does not correspond to a managed NFT.
*/
error NotManagedNFT();
/**
* @dev Error indicating an attempt to reattach an NFT that is already attached to a managed token.
*/
error AlreadyAttached();
/**
* @dev Error indicating a mismatch or incorrect association between user NFTs and managed tokens.
*/
error IncorrectUserNFT();
error AddressZero();
/**
* @notice Error thrown when a provided detachment-lock duration exceeds the allowed maximum.
* @param value The duration (in seconds) that was requested to be set.
* @param max The maximum allowed duration (in seconds).
*/
error DetachmentLockDurationTooLong(uint256 value, uint256 max);
/**
* @dev Represents the state and association of a user's NFT within the management system.
* @notice Stores details about an NFT's attachment status, which managed token it's linked to, and any associated amounts.
*/
struct TokenInfo {
bool isAttached; // Indicates if the NFT is currently attached to a managed strategy.
uint256 attachedManagedTokenId; // The ID of the managed token to which this NFT is attached.
uint256 amount; // The amount associated with this NFT in the context of the managed strategy.
}
/**
* @dev Holds management details about a token within the managed NFT system.
* @notice Keeps track of a managed token's operational status and authorized users.
*/
struct ManagedTokenInfo {
bool isManaged; // True if the token is recognized as a managed token.
bool isDisabled; // Indicates if the token is currently disabled and not operational.
address authorizedUser; // Address authorized to perform restricted operations for this managed token.
}
/**
* @dev Role identifier for administrative functions within the NFT management context.
*/
bytes32 public constant MANAGED_NFT_ADMIN = keccak256("MANAGED_NFT_ADMIN");
/**
* @notice Address of the Voting Escrow contract managing voting and staking mechanisms.
*/
address public override votingEscrow;
/**
* @notice Address of the Voter contract responsible for handling governance actions related to managed NFTs.
*/
address public override voter;
/**
* @notice Tracks detailed information about individual tokens.
*/
mapping(uint256 => TokenInfo) public tokensInfo;
/**
* @notice Maintains management state for managed tokens.
*/
mapping(uint256 => ManagedTokenInfo) public managedTokensInfo;
/**
* @notice Tracks whitelisting status of NFTs to control their eligibility within the system.
*/
mapping(uint256 => bool) public override isWhitelistedNFT;
/**
* @notice Retrieves the strategy flags for a given strategy.
*/
mapping(address => uint8) public override getStrategyFlags;
/**
* @notice Default duration (in seconds) of the lock window that prevents detaching/withdrawing after epoch start.
* @dev Strategies should read this value as the baseline window unless an explicit per-strategy override applies.
*/
uint256 public override defaultDetachmentLockDuration;
/**
* @notice Upper bound for the default detachment-lock duration: 6 days.
* @dev Used as a hard cap in {setDefaultDetachmentLockDuration}.
*/
uint256 internal constant _MAX_DETACHMENT_LOCK_DURATION = 6 days;
/**
* @dev Ensures that the function can only be called by the designated voter address.
*/
modifier onlyVoter() {
if (_msgSender() != voter) {
revert AccessDenied();
}
_;
}
/**
* @dev Ensures that the function can only be called by the designated voting escrow address.
*/
modifier onlyVotingEscrow() {
if (_msgSender() != votingEscrow) {
revert AccessDenied();
}
_;
}
/**
* @dev Constructor that disables initialization on implementation.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the Managed NFT Manager contract
* @param votingEscrow_ The address of the voting escrow contract
* @param voter_ The address of the voter contract
*/
function initialize(address votingEscrow_, address voter_) external initializer {
__AccessControl_init();
_checkAddressZero(votingEscrow_);
_checkAddressZero(voter_);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MANAGED_NFT_ADMIN, msg.sender);
votingEscrow = votingEscrow_;
voter = voter_;
}
/**
* @notice Set the default duration (in seconds) of the detachment/withdrawal lock window for strategies.
* @dev
* - The new duration must not exceed {MAX_DETACHMENT_LOCK_DURATION}.
* - Updates the public state variable {defaultDetachmentLockDuration}.
* - Emits {SetDefaultDetachmentLockDuration} on success.
* @param newDuration_ The new default lock duration, in seconds.
*/
function setDefaultDetachmentLockDuration(uint256 newDuration_)
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
if (newDuration_ > _MAX_DETACHMENT_LOCK_DURATION) {
revert DetachmentLockDurationTooLong(newDuration_, _MAX_DETACHMENT_LOCK_DURATION);
}
uint256 old = defaultDetachmentLockDuration;
defaultDetachmentLockDuration = newDuration_;
emit SetDefaultDetachmentLockDuration(old, newDuration_);
}
/**
* @notice Creates a managed NFT and attaches it to a strategy
* @param strategy_ The strategy to which the managed NFT will be attached
*/
function createManagedNFT(address strategy_) external onlyRole(MANAGED_NFT_ADMIN) returns (uint256 managedTokenId) {
managedTokenId = IVotingEscrow(votingEscrow).createManagedNFT(strategy_);
managedTokensInfo[managedTokenId] = ManagedTokenInfo(true, false, address(0));
IManagedNFTStrategy(strategy_).attachManagedNFT(managedTokenId);
emit CreateManagedNFT(msg.sender, strategy_, managedTokenId);
}
/**
* @notice Updates the strategy flags for a specific strategy.
* @dev Sets the flags for the given strategy and emits the `SetStrategyFlags` event.
* This function can only be called by an account with the `MANAGED_NFT_ADMIN` role.
* @param strategy_ The address of the strategy to update.
* @param flags_ The new flags to assign to the strategy.
* @custom:emits SetStrategyFlags
* - When the strategy flags are updated, this event is emitted with the new flags and the strategy address.
* @custom:requirements
* - The caller must have the `MANAGED_NFT_ADMIN` role.
*/
function setStrategyFlags(address strategy_, uint8 flags_) external onlyRole(MANAGED_NFT_ADMIN) {
getStrategyFlags[strategy_] = flags_;
emit SetStrategyFlags(strategy_, flags_);
}
/**
* @notice Authorizes a user for a specific managed token ID
* @param managedTokenId_ The token ID to authorize
* @param authorizedUser_ The user being authorized
*/
function setAuthorizedUser(uint256 managedTokenId_, address authorizedUser_) external onlyRole(MANAGED_NFT_ADMIN) {
if (!managedTokensInfo[managedTokenId_].isManaged) {
revert NotManagedNFT();
}
managedTokensInfo[managedTokenId_].authorizedUser = authorizedUser_;
emit SetAuthorizedUser(managedTokenId_, authorizedUser_);
}
/**
* @notice Toggles the disabled state of a managed NFT
* @param managedTokenId_ The ID of the managed token to toggle
* @dev Enables or disables a managed token to control its operational status, with an event emitted for state change.
*/
function toggleDisableManagedNFT(uint256 managedTokenId_) external onlyRole(MANAGED_NFT_ADMIN) {
if (!managedTokensInfo[managedTokenId_].isManaged) {
revert NotManagedNFT();
}
bool isDisable = !managedTokensInfo[managedTokenId_].isDisabled;
managedTokensInfo[managedTokenId_].isDisabled = isDisable;
emit ToggleDisableManagedNFT(msg.sender, managedTokenId_, isDisable);
}
/**
* @notice Handles the deposit of tokens to an NFT attached to a managed token.
* @dev Called by the Voting Escrow contract when tokens are deposited to an NFT that is attached to a managed NFT.
* The function verifies the token is attached, checks if it is disabled, and updates the token's state.
* @param tokenId_ The token ID of the user's NFT.
* @param amount_ The amount of tokens to deposit.
* @custom:error IncorrectUserNFT Thrown if the provided token ID is not attached or if it is a managed token itself.
* @custom:error ManagedNFTIsDisabled Thrown if the managed token is currently disabled.
*/
function onDepositToAttachedNFT(uint256 tokenId_, uint256 amount_) external onlyVotingEscrow {
if (!tokensInfo[tokenId_].isAttached || managedTokensInfo[tokenId_].isManaged) {
revert IncorrectUserNFT();
}
uint256 managedTokenId = tokensInfo[tokenId_].attachedManagedTokenId;
if (managedTokensInfo[managedTokenId].isDisabled) {
revert ManagedNFTIsDisabled();
}
tokensInfo[tokenId_].amount += amount_;
IManagedNFTStrategy(IVotingEscrow(votingEscrow).ownerOf(managedTokenId)).onAttach(tokenId_, amount_);
}
/**
* @notice Handler for attaching to a managed NFT
* @param tokenId_ The token ID of the user's NFT
* @param managedTokenId_ The managed token ID to attach to
*/
function onAttachToManagedNFT(uint256 tokenId_, uint256 managedTokenId_) external onlyVoter {
ManagedTokenInfo memory managedTokenInfo = managedTokensInfo[managedTokenId_];
if (!managedTokenInfo.isManaged) {
revert NotManagedNFT();
}
if (managedTokenInfo.isDisabled) {
revert ManagedNFTIsDisabled();
}
if (managedTokensInfo[tokenId_].isManaged || tokensInfo[tokenId_].isAttached) {
revert IncorrectUserNFT();
}
uint256 userBalance = IVotingEscrow(votingEscrow).onAttachToManagedNFT(tokenId_, managedTokenId_);
tokensInfo[tokenId_] = TokenInfo(true, managedTokenId_, userBalance);
IManagedNFTStrategy(IVotingEscrow(votingEscrow).ownerOf(managedTokenId_)).onAttach(tokenId_, userBalance);
}
/**
* @notice Handler for detaching from a managed NFT
* @param tokenId_ The token ID of the user's NFT
*/
function onDettachFromManagedNFT(uint256 tokenId_) external onlyVoter {
TokenInfo memory tokenInfo = tokensInfo[tokenId_];
if (!tokenInfo.isAttached) {
revert NotAttached();
}
assert(tokenInfo.attachedManagedTokenId != 0);
uint256 lockedRewards = IManagedNFTStrategy(IVotingEscrow(votingEscrow).ownerOf(tokenInfo.attachedManagedTokenId)).onDettach(
tokenId_,
tokenInfo.amount
);
IVotingEscrow(votingEscrow).onDettachFromManagedNFT(tokenId_, tokenInfo.attachedManagedTokenId, tokenInfo.amount + lockedRewards);
delete tokensInfo[tokenId_];
}
/**
* @notice Sets or unsets an NFT as whitelisted
* @param tokenId_ The token ID of the NFT
* @param isWhitelisted_ True if whitelisting, false otherwise
*/
function setWhitelistedNFT(uint256 tokenId_, bool isWhitelisted_) external onlyRole(MANAGED_NFT_ADMIN) {
isWhitelistedNFT[tokenId_] = isWhitelisted_;
emit SetWhitelistedNFT(tokenId_, isWhitelisted_);
}
/**
* @notice Retrieves the managed token ID attached to a specific user NFT.
* @dev Returns the managed token ID to which the user's NFT is currently attached.
* @param tokenId_ The token ID of the user's NFT.
* @return The ID of the managed token to which the NFT is attached.
*/
function getAttachedManagedTokenId(uint256 tokenId_) external view returns (uint256) {
return tokensInfo[tokenId_].attachedManagedTokenId;
}
/**
* @notice Checks if a specific user NFT is currently attached to a managed token.
* @dev Returns true if the user's NFT is attached to any managed token.
* @param tokenId_ The token ID of the user's NFT.
* @return True if the NFT is attached, false otherwise.
*/
function isAttachedNFT(uint256 tokenId_) external view returns (bool) {
return tokensInfo[tokenId_].isAttached;
}
/**
* @notice Determines if a managed token is currently disabled.
* @dev Checks the disabled status of a managed token to prevent operations during maintenance or shutdown periods.
* @param managedTokenId_ The ID of the managed token.
* @return True if the managed token is disabled, false otherwise.
*/
function isDisabledNFT(uint256 managedTokenId_) external view returns (bool) {
return managedTokensInfo[managedTokenId_].isDisabled;
}
/**
* @notice Checks if a given address has administrative privileges.
* @dev Determines whether an address holds the MANAGED_NFT_ADMIN role, granting administrative capabilities.
* @param account_ The address to check for administrative privileges.
* @return True if the address has administrative privileges, false otherwise.
*/
function isAdmin(address account_) external view returns (bool) {
return account_ == address(this) || super.hasRole(MANAGED_NFT_ADMIN, account_);
}
/**
* @notice Checks if a user is authorized to interact with a specific managed token.
* @dev Determines whether an address is the designated authorized user for a managed token.
* @param managedTokenId_ The ID of the managed token.
* @param account_ The address to verify authorization.
* @return True if the address is authorized, false otherwise.
*/
function isAuthorized(uint256 managedTokenId_, address account_) external view returns (bool) {
return managedTokensInfo[managedTokenId_].authorizedUser == account_;
}
/**
* @notice Determines if a token ID corresponds to a managed NFT within the system.
* @dev Checks the management status of a token ID to validate its inclusion in managed operations.
* @param managedTokenId_ The ID of the token to check.
* @return True if the token is a managed NFT, false otherwise.
*/
function isManagedNFT(uint256 managedTokenId_) external view override returns (bool) {
return managedTokensInfo[managedTokenId_].isManaged;
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
contracts/utils/GetInformationAggregatorUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import "../core/interfaces/IVoter.sol";
import "../core/interfaces/IVotingEscrow.sol";
import "../dexV2/interfaces/IPairFactory.sol";
import "../dexV2/interfaces/IPair.sol";
import "../gauges/interfaces/IGauge.sol";
import "../bribes/interfaces/IBribe.sol";
import "../lute/interfaces/ISingelTokenVirtualRewarder.sol";
import "../lute/interfaces/IManagedNFTManager.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
interface IExtendVoter is IVoter {
function pools(uint256 index) external view returns (address);
function totalWeightsPerEpoch(uint256 epoch) external view returns (uint256);
}
interface IExtendGauge is IGauge {
function rewardRate() external view returns (uint256);
function rewardForDuration() external view returns (uint256);
}
contract GetInformationAggregatorUpgradeable {
enum AddressKey {
NONE,
VOTING_ESCROW,
VOTER,
PAIR_FACTORY,
MANAGED_NFT_MANAGER
}
address public immutable owner;
mapping(AddressKey => address) public registry;
uint256 constant WEEK = 604800;
constructor() {
owner = msg.sender;
}
function updateAddress(AddressKey[] calldata keys_, address[] calldata values_) external {
require(owner == msg.sender, "AccessDenied");
for (uint256 i; i < keys_.length; ) {
registry[keys_[i]] = values_[i];
unchecked {
i++;
}
}
}
struct TempPoolInfo {
address pool;
address internalBribe;
}
struct TokenVoteInfo {
address pool;
uint256 weight;
uint256 totalWeight;
}
struct TokenVotesPerEpoch {
uint256 tokenId;
address currentOwner;
bool isPermanentLocked;
bool isAttached;
uint256 end;
uint256 lastVotedTimestamp;
uint256 currentEpochTokenVotePower;
uint256 sumWeightFromBribe;
bool isManagedNFT;
bool exists;
TokenVoteInfo[] votes;
}
function getTokenIdsVotesPerEpoch(
uint256[] calldata tokenIds_,
uint256 epoch_,
uint256 limit_,
uint256 offset_
) external view returns (TokenVotesPerEpoch[] memory result) {
IExtendVoter voter = IExtendVoter(registry[AddressKey.VOTER]);
IVotingEscrow votingEscow = IVotingEscrow(registry[AddressKey.VOTING_ESCROW]);
IManagedNFTManager managedNftManager = IManagedNFTManager(registry[AddressKey.MANAGED_NFT_MANAGER]);
require(epoch_ % WEEK == 0, "invalid epoch");
(uint256 totalCount, , ) = voter.poolsCounts();
uint256 size = totalCount;
if (offset_ >= size) {
size = 0;
}
size -= offset_;
if (size > limit_) {
size = limit_;
}
TempPoolInfo[] memory pools = new TempPoolInfo[](size);
for (uint256 j; j < size; j++) {
pools[j].pool = IExtendVoter(address(voter)).pools(j + offset_);
IVoter.GaugeState memory state = voter.getGaugeState(voter.poolToGauge(pools[j].pool));
pools[j].internalBribe = state.internalBribe;
}
result = new TokenVotesPerEpoch[](tokenIds_.length);
TokenVoteInfo[] memory tempArray = new TokenVoteInfo[](size);
uint256 countVotes;
for (uint256 i; i < tokenIds_.length; i++) {
TokenVotesPerEpoch memory info;
info.tokenId = tokenIds_[i];
IVotingEscrow.LockedBalance memory locked = votingEscow.getNftState(info.tokenId).locked;
info.isPermanentLocked = locked.isPermanentLocked;
info.end = locked.end;
info.isManagedNFT = managedNftManager.isManagedNFT(info.tokenId);
info.currentEpochTokenVotePower = votingEscow.balanceOfNftIgnoreOwnershipChange(info.tokenId);
info.lastVotedTimestamp = voter.lastVotedTimestamps(info.tokenId);
info.isAttached = managedNftManager.isAttachedNFT(info.tokenId);
info.sumWeightFromBribe;
try votingEscow.ownerOf(info.tokenId) returns (address) {
info.currentOwner = votingEscow.ownerOf(info.tokenId);
info.exists = true;
} catch {}
if (info.exists) {
for (uint256 j; j < pools.length; j++) {
IBribe internalBribe = IBribe(pools[j].internalBribe);
uint256 votePower = internalBribe.balanceOfAt(info.tokenId, epoch_);
if (votePower > 0) {
tempArray[countVotes].pool = pools[j].pool;
tempArray[countVotes].weight = votePower;
tempArray[countVotes].totalWeight = internalBribe.totalSupplyAt(epoch_);
info.sumWeightFromBribe += votePower;
countVotes++;
}
}
}
info.votes = new TokenVoteInfo[](countVotes);
for (uint256 j; j < countVotes; j++) {
info.votes[j].pool = tempArray[j].pool;
info.votes[j].weight = tempArray[j].weight;
info.votes[j].totalWeight = tempArray[j].totalWeight;
}
result[i] = info;
countVotes = 0;
}
return result;
}
struct BribeTotalVoteInfo {
address bribe;
uint256 totalSupply;
}
struct GaugeCurrentTotalVoteInfo {
uint256 rewardRate;
uint256 rewardForDuration;
}
struct PoolEpochVoteInfo {
string name;
address pool;
address gauge;
uint256 weightsPerEpoch;
uint256 emissionToGauge;
GaugeCurrentTotalVoteInfo gaugeState;
BribeTotalVoteInfo internalBribe;
BribeTotalVoteInfo externalBribe;
}
struct PoolsEpochVoteInfoGeneral {
uint256 poolsCount;
uint256 totalWeightsPerEpoch;
uint256 epoch;
uint256 emisisonPerEpoch;
PoolEpochVoteInfo[] poolsEpochVoteInfo;
}
function getGeneralVotesPerEpoch(
uint256 epoch_,
uint256 emisisonPerEpoch_,
uint256 limit_,
uint256 offset_
) external view returns (PoolsEpochVoteInfoGeneral memory result) {
IExtendVoter voter = IExtendVoter(registry[AddressKey.VOTER]);
IPairFactory pairFactory = IPairFactory(registry[AddressKey.PAIR_FACTORY]);
require(epoch_ % WEEK == 0, "invalid epoch");
(uint256 totalCount, , ) = voter.poolsCounts();
uint256 size = totalCount;
if (offset_ >= size) {
result.poolsEpochVoteInfo = new PoolEpochVoteInfo[](0);
return result;
}
size -= offset_;
if (size > limit_) {
size = limit_;
}
result.poolsCount = totalCount;
result.totalWeightsPerEpoch = voter.totalWeightsPerEpoch(epoch_);
result.emisisonPerEpoch = emisisonPerEpoch_;
result.epoch = epoch_;
result.poolsEpochVoteInfo = new PoolEpochVoteInfo[](size);
for (uint256 i; i < size; ) {
PoolEpochVoteInfo memory info;
info.pool = IExtendVoter(address(voter)).pools(i + offset_);
info.gauge = voter.poolToGauge(info.pool);
info.weightsPerEpoch = voter.weightsPerEpoch(epoch_, info.pool);
info.name = _getPoolName(pairFactory, info.pool);
info.emissionToGauge = (info.weightsPerEpoch * emisisonPerEpoch_) / result.totalWeightsPerEpoch;
IVoter.GaugeState memory state = voter.getGaugeState(info.gauge);
IBribe internalBribe = IBribe(state.internalBribe);
IBribe externalBribe = IBribe(state.externalBribe);
info.internalBribe = BribeTotalVoteInfo(state.internalBribe, internalBribe.totalSupplyAt(epoch_));
info.externalBribe = BribeTotalVoteInfo(state.externalBribe, externalBribe.totalSupplyAt(epoch_));
IExtendGauge gauge = IExtendGauge(info.gauge);
info.gaugeState = GaugeCurrentTotalVoteInfo(gauge.rewardRate(), gauge.rewardForDuration());
result.poolsEpochVoteInfo[i] = info;
unchecked {
i++;
}
}
return result;
}
function _getPoolName(IPairFactory factory_, address pool_) internal view returns (string memory name) {
if (factory_.isPair(pool_)) {
return IPair(pool_).name();
} else {
return
string.concat(
"V3 AlgebraPool - ",
IERC20Metadata(IPair(pool_).token0()).symbol(),
"/",
IERC20Metadata(IPair(pool_).token1()).symbol()
);
}
}
}
contracts/integration/interfaces/IPriceProvider.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title IPriceProvider Interface
* @dev Interface for the price provider that defines a function to retrieve the USD to LUTE price.
*/
interface IPriceProvider {
/**
* @notice Retrieves the current price of 1 USD in LUTE tokens
* @return Price of 1 USD in LUTE tokens.
*/
function getUsdToLUTEPrice() external view returns (uint256);
}
contracts/bribes/BribeProxy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import {IBribeFactory} from "./interfaces/IBribeFactory.sol";
contract BribeProxy {
address private immutable bribeFactory;
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
constructor() {
bribeFactory = msg.sender;
}
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
function _setImplementation(address newImplementation) private {
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
fallback() external payable {
address impl = IBribeFactory(bribeFactory).bribeImplementation();
require(impl != address(0));
//Just for etherscan compatibility
if (impl != _getImplementation() && msg.sender != (address(0))) {
_setImplementation(impl);
}
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
}
contracts/dexV2/VolatileDynamicFeeOnePool.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ICustomVolatileDynamicFee} from "./interfaces/ICustomVolatileDynamicFee.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IPair} from "./interfaces/IPair.sol";
/**
* @title VolatileDynamicFeeOnePool
* @notice Dynamic-fee module for a single pair. The fee starts at `startFeePercentage` (in BPS),
* decreases by `decreaseStepBps` after each `decreaseInterval` seconds, and never goes
* below `finalFeePercentage` (in BPS).
*
* @dev
* - All fee values use basis points (BPS) with `_PRECISION = 10_000` (100%).
* Example: 50% == 5_000, 1% == 100 BPS.
* - Time values are in seconds.
* - The module is bound to exactly one pair (`pair`) at initialization.
* - `startTimestamp` can be provided up-front; if set to 0, it will be lazily initialized
*/
contract VolatileDynamicFeeOnePool is ICustomVolatileDynamicFee, OwnableUpgradeable {
/**
* @notice Precision helper (basis points). 100% == 10_000.
* @dev Not used by default (module uses pp). Available for integrators.
*/
uint256 internal constant _PRECISION = 10000;
/**
* @notice The target pair this fee module is attached to.
*/
address public pair;
/**
* @notice Interval length (in seconds) after which the fee decreases by `decreaseStepBps`.
*/
uint256 public decreaseInterval;
/**
* @notice Fee decrease amount per interval, in BPS (e.g., 100 == 1%).
*/
uint256 public decreaseStepBps;
/**
* @notice Initial fee in BPS (e.g., 5_000 == 50%).
*/
uint256 public startFeePercentage;
/**
* @notice Final (minimum) fee in BPS.
*/
uint256 public finalFeePercentage;
/**
* @notice Start timestamp for the decreasing schedule.
* @dev If set to 0 during initialization, it must be set later before the schedule becomes active.
*/
uint256 public startTimestamp;
/**
* @notice Emitted when `startTimestamp` is updated.
* @param startTimestamp The new schedule start timestamp.
*/
event SetStartTimestamp(uint256 startTimestamp);
/**
* @dev Thrown when a provided address is the zero address.
*/
error AddressZero();
/**
* @dev Thrown when a caller is not authorized to execute the function.
*/
error InvalidCaller();
/**
* @dev Thrown when a function is called for a pair that does not match the configured `pair`.
*/
error InvalidExpectPair();
/**
* @dev Thrown when provided configuration parameters are invalid.
* Examples:
* - `startFeePercentage < finalFeePercentage`
* - `decreaseInterval == 0`
* - `decreaseStepBps == 0`
* - any fee exceeds `_PRECISION`.
*/
error InvalidConfiguration();
/**
* @dev Ensures the function is called strictly for the configured `pair`.
* Reverts with {InvalidExpectPair} if `pair_` does not match.
* @param pair_ The pair address expected to match the configured `pair`.
*/
modifier onlyForOnePair(address pair_) {
if (pair != pair_) {
revert InvalidExpectPair();
}
_;
}
/**
* @notice Disables initializers on implementation to protect upgradeable pattern.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the module for a specific pair with a configurable decreasing fee schedule.
*
* @dev Requirements:
* - `pair_` must be non-zero.
* - `startFeePercentage_` and `finalFeePercentage_` must be within `[0, _PRECISION]`.
* - `startFeePercentage_ > finalFeePercentage_`.
* - `decreaseInterval_ > 0`.
* - `decreaseStepBps_ > 0` and `decreaseStepBps_ <= _PRECISION`.
*
* Emits no events.
*
* @param pair_ The pair address this module controls.
* @param startTimestamp_ Schedule start timestamp (may be 0 to defer activation).
* @param startFeePercentage_ Initial fee in BPS (e.g., 5_000 == 50%).
* @param finalFeePercentage_ Minimal fee in BPS (lower bound).
* @param decreaseInterval_ Interval length in seconds for each decrease step.
* @param decreaseStepBps_ Fee decrease per interval in BPS (e.g., 100 == 1%).
*/
function initialize(
address pair_,
uint256 startTimestamp_,
uint256 startFeePercentage_,
uint256 finalFeePercentage_,
uint256 decreaseInterval_,
uint256 decreaseStepBps_
) external initializer {
_checkAddressZero(pair_);
if (startFeePercentage_ > _PRECISION || finalFeePercentage_ > _PRECISION || startFeePercentage_ <= finalFeePercentage_) {
revert InvalidConfiguration();
}
if (decreaseInterval_ == 0 || decreaseStepBps_ == 0 || decreaseStepBps_ > _PRECISION) {
revert InvalidConfiguration();
}
__Ownable_init();
pair = pair_;
startTimestamp = startTimestamp_;
startFeePercentage = startFeePercentage_;
finalFeePercentage = finalFeePercentage_;
decreaseInterval = decreaseInterval_;
decreaseStepBps = decreaseStepBps_;
}
/**
* @notice Updates the schedule start timestamp.
*
* @dev Requirements:
* - Caller must be the owner.
*
* Emits a {SetStartTimestamp} event.
*
* @param startTimestamp_ The new start timestamp for the schedule.
*/
function setStartTimestamp(uint256 startTimestamp_) external onlyOwner {
startTimestamp = startTimestamp_;
emit SetStartTimestamp(startTimestamp_);
}
/**
* @notice Returns whether the module is considered enabled.
* @dev Current implementation returns `true` unconditionally.
* Integrators that require strict activation gates should implement additional checks.
* @return isEnable_ Always `true`.
*/
function isEnable() public pure override returns (bool isEnable_) {
return true;
}
/**
* @notice Returns the current fee for the configured pair.
* @dev If not active (see {isEnable}), returns `(false, 0)`. Otherwise returns `(true, feeBps)`.
*
* Requirements:
* - `pair_` must match the configured `pair`. (See {onlyForOnePair})
*
* @param pair_ The pair address (must equal the configured `pair`).
* @return success True if the module is active and a fee is provided.
* @return fee The current fee in BPS (e.g., 5_000 == 50%).
*/
function getFee(address pair_) external view override onlyForOnePair(pair_) returns (bool success, uint256 fee) {
if (!isEnable()) {
return (false, 0);
}
success = true;
fee = calculate(block.timestamp, startTimestamp, startFeePercentage, finalFeePercentage, decreaseInterval, decreaseStepBps);
}
/**
* @notice Pure helper to compute the fee (in BPS) for an arbitrary timestamp.
* @dev
* - If `currentTimestamp_ <= startTimestamp_` (or `startTimestamp_ == 0`) the function returns `startFeePercentage_`.
* - For each full `decreaseInterval_` elapsed after the start, the fee is reduced by `decreaseStepBps_`.
* - The result is clamped at `finalFeePercentage_`.
* - Uses ceil-division for the clamp threshold to avoid intermediate multiplication overflow:
* `stepsToMin = ceil((start - final) / step) = (delta + step - 1) / step`.
*
* @param currentTimestamp_ The timestamp to evaluate the fee for.
* @param startTimestamp_ The start of the decreasing schedule.
* @param startFeePercentage_ Initial fee in BPS at schedule start.
* @param finalFeePercentage_ Minimal fee in BPS (clamp lower bound).
* @param decreaseInterval_ Interval length in seconds for each decrease.
* @param decreaseStepBps_ Fee decrease per interval in BPS.
* @return fee The computed fee in BPS for `currentTimestamp_`.
*/
function calculate(
uint256 currentTimestamp_,
uint256 startTimestamp_,
uint256 startFeePercentage_,
uint256 finalFeePercentage_,
uint256 decreaseInterval_,
uint256 decreaseStepBps_
) public pure returns (uint256 fee) {
if (startTimestamp_ == 0 || currentTimestamp_ <= startTimestamp_) {
return startFeePercentage_;
}
uint256 elapsed = currentTimestamp_ - startTimestamp_;
uint256 steps = elapsed / decreaseInterval_;
uint256 deltaBps = startFeePercentage_ - finalFeePercentage_;
uint256 stepsToMin = deltaBps == 0 ? 0 : (deltaBps + decreaseStepBps_ - 1) / decreaseStepBps_;
if (steps >= stepsToMin) {
return finalFeePercentage_;
}
return startFeePercentage_ - steps * decreaseStepBps_;
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert AddressZero();
}
}
}
contracts/mocks/PoolMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
contract PoolMock {
address public token1;
address public token0;
bool public _unlocked;
int24 public _tick;
function setTokens(address token0_, address token1_) external {
token0 = token0_;
token1 = token1_;
}
function setUnlocked(bool unlocked_) external {
_unlocked = unlocked_;
}
function setTick(int24 tick_) external {
_tick = tick_;
}
function globalState()
external
view
returns (uint160 price, int24 tick, uint16 lastFee, uint8 pluginConfig, uint16 communityFee, bool unlocked)
{
tick = _tick;
unlocked = _unlocked;
}
function getTickAtSqrtRatio(uint160 price) external view returns (int24 tick) {
return TickMath.getTickAtSqrtRatio(price);
}
}
@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
@cryptoalgebra/integral-core/contracts/interfaces/plugin/IAlgebraPluginFactory.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title An interface for a contract that is capable of deploying Algebra plugins
/// @dev Such a factory is needed if the plugin should be automatically created and connected to each new pool
interface IAlgebraPluginFactory {
/// @notice Deploys new plugin contract for pool
/// @param pool The address of the pool for which the new plugin will be created
/// @param token0 First token of the pool
/// @param token1 Second token of the pool
/// @return New plugin address
function createPlugin(address pool, address token0, address token1) external returns (address);
}
contracts/bribes/rewards/interfaces/IBribeVeLUTERewardToken.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
/**
* @title IBribeVeLUTERewardToken
* @notice This interface defines the methods and data structures for a token
* that represents an intermediate step in the conversion process of
* LUTE to veLUTE. It allows for minting "bribe"-like tokens, which upon
* transfer to non-whitelisted addresses, are converted into veLUTE locks.
*/
interface IBribeVeLUTERewardToken is IERC20Upgradeable {
/**
* @notice Parameters for creating a veLUTE lock in the VotingEscrow contract.
* @param lockDuration The duration (in seconds) for which the LUTE will be locked.
* @param shouldBoosted Whether the newly created veLUTE position should be boosted.
* @param withPermanentLock If true, the created lock becomes permanent and cannot be unlocked.
* @param managedTokenIdForAttach If non-zero, attaches the newly created veLUTE position to a managed token ID.
*/
struct CreateLockParams {
uint256 lockDuration;
bool shouldBoosted;
bool withPermanentLock;
uint256 managedTokenIdForAttach;
}
/**
* @dev Emitted when the parameters for creating veLUTE locks are updated.
* @param createLockParams The new parameters for creating veLUTE locks.
*/
event UpdateCreateLockParams(CreateLockParams createLockParams);
/**
* @notice Returns the role identifier for entities allowed to mint the intermediate bribe tokens.
* @return A bytes32 value representing the MINTER_ROLE identifier.
*/
function MINTER_ROLE() external view returns (bytes32);
/**
* @notice Returns the role identifier for entities that should not trigger
* automatic conversion of tokens into veLUTE upon receiving them.
* @return A bytes32 value representing the WHITELIST_ROLE identifier.
*/
function WHITELIST_ROLE() external view returns (bytes32);
/**
* @notice Returns the address of the VotingEscrow contract that is used for creating veLUTE locks.
* @return The address of the VotingEscrow contract.
*/
function votingEscrow() external view returns (address);
/**
* @notice Returns the address of the underlying LUTE token that will be locked to create veLUTE positions.
* @return The address of the underlying LUTE token.
*/
function underlyingToken() external view returns (address);
/**
* @notice Returns the parameters currently set for creating new veLUTE locks.
* These parameters are used when converting the intermediary tokens
* into veLUTE by calling the VotingEscrow contract.
* @return lockDuration The duration (in seconds) for which the LUTE will be locked.
* @return shouldBoosted Whether the newly created veLUTE position should be boosted.
* @return withPermanentLock If true, the created lock becomes permanent and cannot be unlocked.
* @return managedTokenIdForAttach If non-zero, attaches the newly created veLUTE position to a managed token ID.
*/
function createLockParams()
external
view
returns (uint256 lockDuration, bool shouldBoosted, bool withPermanentLock, uint256 managedTokenIdForAttach);
/**
* @notice Updates the parameters used for creating new veLUTE locks.
* @dev Only callable by an address holding the DEFAULT_ADMIN_ROLE.
* This function sets new values that dictate how veLUTE locks are created when
* intermediary tokens are transferred to non-whitelisted addresses.
* @param createLockParams_ The new parameters specifying lock duration, boosting,
* permanent lock setting, and associated managed token ID.
*
* Emits an {UpdateCreateLockParams} event.
*/
function updateCreateLockParams(CreateLockParams memory createLockParams_) external;
/**
* @notice Mints a specified amount of bribe-like tokens (intermediary tokens)
* in exchange for LUTE tokens transferred to this contract.
* Only accounts with the MINTER_ROLE can call this function.
* @dev The LUTE tokens must be transferred before or during the call.
* The contract will hold these LUTE tokens until they are eventually
* converted into veLUTE locks.
* @param to_ The address that will receive the newly minted intermediary tokens.
* @param amount_ The number of tokens to mint, which corresponds to the LUTE
* amount locked within this contract for future veLUTE conversion.
*/
function mint(address to_, uint256 amount_) external;
}
@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
pragma solidity ^0.8.0;
import "../IERC721Upgradeable.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721EnumerableUpgradeable is IERC721Upgradeable {
/**
* @dev Returns the total amount of tokens stored by the contract.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns a token ID owned by `owner` at a given `index` of its token list.
* Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
/**
* @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
* Use along with {totalSupply} to enumerate all tokens.
*/
function tokenByIndex(uint256 index) external view returns (uint256);
}
contracts/core/libraries/LibVotingEscrowErrors.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
/**
* @notice Reverts when an operation is attempted on a token that has been used to vote.
*/
error TokenVoted();
/**
* @notice Reverts when an operation is attempted with a value of zero, where a non-zero value is required.
*/
error ValueZero();
/**
* @notice Reverts when an operation is attempted on a non-existent token.
*/
error TokenNotExist();
/**
* @notice Reverts when an operation is attempted on a token that has not yet expired.
*/
error TokenExpired();
/**
* @notice Reverts when an invalid lock duration is provided.
*/
error InvalidLockDuration();
/**
* @notice Reverts when an operation is attempted on a permanently locked token.
*/
error PermanentLocked();
/**
* @notice Reverts when an operation is attempted on a token that is not expired.
*/
error TokenNoExpired();
/**
* @notice Reverts when an operation is attempted on a token that is not permanently locked.
*/
error NotPermanentLocked();
/**
* @notice Reverts when an invalid address key is provided.
*/
error InvalidAddressKey();
/**
* @notice Reverts when a merge operation is attempted with the same token IDs.
*/
error MergeTokenIdsTheSame();
/**
* @notice Reverts when a merge operation is attempted with tokens that have different owners.
*/
error OwnerNotSame();
/**
* @notice Reverts when access is denied for the operation.
*/
error AccessDenied();
/**
* @notice Reverts when an operation is attempted on a token with zero voting power.
*/
error ZeroVotingPower();
/**
* @notice Reverts when an operation is attempted on a non-managed NFT.
*/
error NotManagedNft();
/**
* @notice Reverts when a transfer is attempted on a managed NFT.
*/
error ManagedNftTransferDisabled();
/**
* @notice Reverts when a gauge already exists for a pool.
*/
error GaugeForPoolAlreadyExists();
/**
* @notice Reverts when an operation is attempted on an attached token.
*/
error TokenAttached();
/**
* @notice Reverts when an operation is attempted on a token that is not attached.
*/
error TokenNotAttached();
@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165Upgradeable.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721Upgradeable is IERC165Upgradeable {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
contracts/dexV2/interfaces/IRouterV2.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
interface IRouterV2 {
struct route {
address from;
address to;
bool stable;
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
route[] calldata routes,
address to,
uint deadline
) external returns (uint[] memory amounts);
function pairFor(address tokenA, address tokenB, bool stable) external view returns (address pair);
function getAmountsOut(uint amountIn, route[] memory routes) external view returns (uint[] memory amounts);
function getAmountOut(uint amountIn, address tokenIn, address tokenOut) external view returns (uint amount, bool stable);
}
contracts/core/interfaces/IVotingEscrow.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
/**
* @title IVotingEscrow
* @notice Interface for Voting Escrow, allowing users to lock tokens in exchange for veNFTs that are used in governance and other systems.
*/
interface IVotingEscrow is IERC721Upgradeable {
/**
* @notice Enum representing the types of deposits that can be made.
* @dev Defines the context in which a deposit is made:
* - `DEPOSIT_FOR_TYPE`: Regular deposit for an existing lock.
* - `CREATE_LOCK_TYPE`: Creating a new lock.
* - `INCREASE_UNLOCK_TIME`: Increasing the unlock time for an existing lock.
* - `MERGE_TYPE`: Merging two locks together.
*/
enum DepositType {
DEPOSIT_FOR_TYPE,
CREATE_LOCK_TYPE,
INCREASE_UNLOCK_TIME,
MERGE_TYPE
}
/**
* @notice Structure representing the state of a token.
* @dev This includes information about the lock, voting status, attachment status,
* the block of the last transfer, and the index (epoch) of its latest checkpoint.
* @param locked The locked balance (amount + end timestamp + permanent status) of the token.
* @param isVoted Whether the token has been used to vote in the current epoch.
* @param isAttached Whether the token is attached to a managed NFT.
* @param lastTranferBlock The block number of the last transfer.
* @param pointEpoch The epoch (checkpoint index) for the token’s most recent voting power change.
*/
struct TokenState {
LockedBalance locked;
bool isVoted;
bool isAttached;
uint256 lastTranferBlock;
uint256 pointEpoch;
}
/**
* @notice Structure representing a locked balance.
* @dev Contains the amount locked, the end timestamp of the lock, and whether the lock is permanent.
* @param amount The amount of tokens locked (signed integer for slope calculations).
* @param end The timestamp when the lock ends (0 if permanently locked).
* @param isPermanentLocked Whether the lock is permanent (no unlock time).
*/
struct LockedBalance {
int128 amount;
uint256 end;
bool isPermanentLocked;
}
/**
* @notice Structure representing a point in time for calculating voting power.
* @dev Used for slope/bias math across epochs.
* @param bias The bias of the lock, representing the remaining voting power.
* @param slope The rate at which voting power (bias) decays over time.
* @param ts The timestamp of the checkpoint.
* @param blk The block number of the checkpoint.
* @param permanent The permanently locked amount at this checkpoint.
*/
struct Point {
int128 bias;
int128 slope; // -dweight / dt
uint256 ts;
uint256 blk; // block
int128 permanent;
}
/**
* @notice Emitted when a boost is applied to a token's lock.
* @param tokenId The ID of the token that received the boost.
* @param value The amount of tokens used as a boost.
*/
event Boost(uint256 indexed tokenId, uint256 value);
/**
* @notice Emitted when a deposit is made into a lock.
* @param provider The address of the entity making the deposit.
* @param tokenId The ID of the token associated with the deposit.
* @param value The amount of tokens deposited.
* @param locktime The time (timestamp) until which the lock is extended.
* @param deposit_type The type of deposit (see {DepositType}).
* @param ts The timestamp when the deposit was made.
*/
event Deposit(address indexed provider, uint256 tokenId, uint256 value, uint256 indexed locktime, DepositType deposit_type, uint256 ts);
/**
* @notice Emitted when tokens are deposited to an attached NFT.
* @param provider The address of the user making the deposit.
* @param tokenId The ID of the NFT receiving the deposit.
* @param managedTokenId The ID of the managed token receiving the voting power.
* @param value The amount of tokens deposited.
*/
event DepositToAttachedNFT(address indexed provider, uint256 tokenId, uint256 managedTokenId, uint256 value);
/**
* @notice Emitted when a withdrawal is made from a lock.
* @param provider The address of the entity making the withdrawal.
* @param tokenId The ID of the token associated with the withdrawal.
* @param value The amount of tokens withdrawn.
* @param ts The timestamp when the withdrawal occurred.
*/
event Withdraw(address indexed provider, uint256 tokenId, uint256 value, uint256 ts);
/**
* @notice Emitted when the merging process of two veNFT locks is initiated.
* @param tokenFromId The ID of the token being merged from.
* @param tokenToId The ID of the token being merged into.
*/
event MergeInit(uint256 tokenFromId, uint256 tokenToId);
/**
* @notice Emitted when two veNFT locks are successfully merged.
* @param provider The address of the entity initiating the merge.
* @param tokenIdFrom The ID of the token being merged from.
* @param tokenIdTo The ID of the token being merged into.
*/
event Merge(address indexed provider, uint256 tokenIdFrom, uint256 tokenIdTo);
/**
* @notice Emitted when the total supply of voting power changes.
* @param prevSupply The previous total supply of voting power.
* @param supply The new total supply of voting power.
*/
event Supply(uint256 prevSupply, uint256 supply);
/**
* @notice Emitted when an address associated with the contract is updated.
* @param key The key representing the contract being updated.
* @param value The new address of the contract.
*/
event UpdateAddress(string key, address indexed value);
/**
* @notice Emitted when a token is permanently locked by a user.
* @param sender The address of the user who initiated the lock.
* @param tokenId The ID of the token that has been permanently locked.
*/
event LockPermanent(address indexed sender, uint256 indexed tokenId);
/**
* @notice Emitted when a token is unlocked from a permanent lock state by a user.
* @param sender The address of the user who initiated the unlock.
* @param tokenId The ID of the token that has been unlocked from its permanent state.
*/
event UnlockPermanent(address indexed sender, uint256 indexed tokenId);
/**
* @notice Emitted when a veLUTE NFT lock is burned and the underlying LUTE is released for use in bribes.
* @param sender The address which initiated the burn-to-bribes operation.
* @param tokenId The identifier of the veLUTE NFT that was burned.
* @param value The amount of LUTE tokens released from the burned lock.
*/
event BurnToBribes(address indexed sender, uint256 indexed tokenId, uint256 value);
/**
* @notice Returns the address of the token used in voting escrow.
* @return The address of the token contract.
*/
function token() external view returns (address);
/**
* @notice Returns the address of the voter contract.
* @return The address of the voter.
*/
function voter() external view returns (address);
/**
* @notice Checks if the specified address is approved or the owner of the given token.
* @param sender The address to check.
* @param tokenId The ID of the token to check.
* @return True if `sender` is approved or the owner of `tokenId`, otherwise false.
*/
function isApprovedOrOwner(address sender, uint256 tokenId) external view returns (bool);
/**
* @notice Checks if a specific NFT token is transferable.
* @dev In the current implementation, this function always returns `true`,
* meaning the contract does not enforce non-transferability at code level.
* @param tokenId_ The ID of the NFT to check.
* @return bool Always returns true in the current version.
*/
function isTransferable(uint256 tokenId_) external view returns (bool);
/**
* @notice Retrieves the state of a specific NFT.
* @param tokenId_ The ID of the NFT to query.
* @return The current {TokenState} of the specified NFT.
*/
function getNftState(uint256 tokenId_) external view returns (TokenState memory);
/**
* @notice Returns the total supply of voting power at the current block timestamp.
* @return The total supply of voting power.
*/
function votingPowerTotalSupply() external view returns (uint256);
/**
* @notice Returns the balance of a veNFT at the current block timestamp.
* @dev Balance is determined by the lock’s slope and bias at this moment.
* @param tokenId_ The ID of the veNFT to query.
* @return The current voting power (balance) of the veNFT.
*/
function balanceOfNFT(uint256 tokenId_) external view returns (uint256);
/**
* @notice Returns the balance of a veNFT at the current block timestamp, ignoring ownership changes.
* @dev This function is similar to {balanceOfNFT} but does not zero out the balance
* if the token was transferred in the same block.
* @param tokenId_ The ID of the veNFT to query.
* @return The current voting power (balance) of the veNFT.
*/
function balanceOfNftIgnoreOwnershipChange(uint256 tokenId_) external view returns (uint256);
/**
* @notice Updates the address of a specified contract.
* @param key_ The key representing the contract.
* @param value_ The new address of the contract.
* @dev Reverts with `InvalidAddressKey` if the key does not match any known setting.
* Emits an {UpdateAddress} event on success.
*/
function updateAddress(string memory key_, address value_) external;
/**
* @notice Hooks the voting state for a specified NFT.
* @dev Only callable by the voter contract. Used to mark a veNFT as having voted or not.
* @param tokenId_ The ID of the NFT.
* @param state_ True if the NFT is now considered “voted,” false otherwise.
* @custom:error AccessDenied If called by any address other than the voter.
*/
function votingHook(uint256 tokenId_, bool state_) external;
/**
* @notice Creates a new lock for a specified recipient.
* @param amount_ The amount of tokens to lock.
* @param lockDuration_ The duration in seconds for which the tokens will be locked.
* @param to_ The address of the recipient who will receive the new veNFT.
* @param shouldBoosted_ Whether the deposit should attempt to get a veBoost.
* @param withPermanentLock_ Whether the lock should be created as a permanent lock.
* @param managedTokenIdForAttach_ (Optional) The ID of the managed NFT to attach. Pass 0 to ignore.
* @return The ID of the newly created veNFT.
* @dev Reverts with `InvalidLockDuration` if lockDuration_ is 0 or too large.
* Reverts with `ValueZero` if amount_ is 0.
* Emits a {Deposit} event on success.
*/
function createLockFor(
uint256 amount_,
uint256 lockDuration_,
address to_,
bool shouldBoosted_,
bool withPermanentLock_,
uint256 managedTokenIdForAttach_
) external returns (uint256);
/**
* @notice Deposits tokens for a specific NFT, increasing its locked balance.
* @param tokenId_ The ID of the veNFT to top up.
* @param amount_ The amount of tokens to deposit.
* @param shouldBoosted_ Whether this deposit should attempt to get a veBoost.
* @param withPermanentLock_ Whether to apply a permanent lock alongside the deposit.
* @dev Reverts with `ValueZero` if amount_ is 0.
* Emits a {Deposit} event upon success.
*/
function depositFor(uint256 tokenId_, uint256 amount_, bool shouldBoosted_, bool withPermanentLock_) external;
/**
* @notice Increases the unlock time for an existing lock.
* @param tokenId_ The ID of the veNFT to extend.
* @param lockDuration_ The additional duration in seconds to add to the current unlock time.
* @dev Reverts with `InvalidLockDuration` if the new unlock time is invalid.
* Reverts with `AccessDenied` if the caller is not the owner or approved.
* Emits a {Deposit} event with the deposit type set to {INCREASE_UNLOCK_TIME}.
*/
function increase_unlock_time(uint256 tokenId_, uint256 lockDuration_) external;
/**
* @notice Deposits tokens and extends the lock duration for a veNFT in one call.
* @dev This may trigger veBoost if conditions are met.
* @param tokenId_ The ID of the veNFT.
* @param amount_ The amount of tokens to deposit.
* @param lockDuration_ The duration in seconds to add to the current unlock time.
* Emits one {Deposit} event for the deposit itself
* and another {Deposit} event for the unlock time increase.
*/
function depositWithIncreaseUnlockTime(uint256 tokenId_, uint256 amount_, uint256 lockDuration_) external;
/**
* @notice Deposits tokens directly into a veNFT that is attached to a managed NFT.
* @dev This updates the locked balance on the managed NFT, adjusts total supply,
* and emits {DepositToAttachedNFT} and {Supply} events.
* @param tokenId_ The ID of the attached veNFT.
* @param amount_ The amount of tokens to deposit.
* @custom:error NotManagedNft if the managed token ID is invalid or not recognized.
*/
function depositToAttachedNFT(uint256 tokenId_, uint256 amount_) external;
/**
* @notice Withdraws tokens from an expired lock (non-permanent).
* @param tokenId_ The ID of the veNFT to withdraw from.
* @dev Reverts with `AccessDenied` if caller is not owner or approved.
* Reverts with `TokenNoExpired` if the lock is not yet expired.
* Reverts with `PermanentLocked` if the lock is permanent.
* Emits a {Withdraw} event and a {Supply} event.
*/
function withdraw(uint256 tokenId_) external;
/**
* @notice Merges one veNFT (tokenFromId_) into another (tokenToId_).
* @param tokenFromId_ The ID of the source veNFT being merged.
* @param tokenToId_ The ID of the target veNFT receiving the locked tokens.
* @dev Reverts with `MergeTokenIdsTheSame` if both IDs are the same.
* Reverts with `AccessDenied` if the caller isn't owner or approved for both IDs.
* Emits a {MergeInit} event at the start, and a {Merge} event upon completion.
* Also emits a {Deposit} event reflecting the updated lock in the target token.
*/
function merge(uint256 tokenFromId_, uint256 tokenToId_) external;
/**
* @notice Permanently locks a veNFT.
* @param tokenId_ The ID of the veNFT to be permanently locked.
* @dev Reverts with `AccessDenied` if caller isn't owner or approved.
* Reverts with `TokenAttached` if the token is attached to a managed NFT.
* Reverts with `PermanentLocked` if the token already permanent lcoked
* Emits {LockPermanent} on success.
*/
function lockPermanent(uint256 tokenId_) external;
/**
* @notice Unlocks a permanently locked veNFT, reverting it to a temporary lock.
* @param tokenId_ The ID of the veNFT to unlock.
* @dev Reverts with `AccessDenied` if caller isn't owner or approved.
* Reverts with `TokenAttached` if the token is attached.
* Reverts with `NotPermanentLocked` if the lock isn't actually permanent.
* Emits {UnlockPermanent} on success.
*/
function unlockPermanent(uint256 tokenId_) external;
/**
* @notice Creates a new managed NFT for a given recipient.
* @param recipient_ The address that will receive the newly created managed NFT.
* @return The ID of the newly created managed NFT.
* @dev Reverts with `AccessDenied` if caller is not the managed NFT manager.
*/
function createManagedNFT(address recipient_) external returns (uint256);
/**
* @notice Attaches a veNFT (user’s token) to a managed NFT, combining their locked balances.
* @param tokenId_ The ID of the user’s veNFT being attached.
* @param managedTokenId_ The ID of the managed NFT.
* @return The amount of tokens locked during the attachment.
* @dev Reverts with `AccessDenied` if caller is not the managed NFT manager.
* Reverts with `ZeroVotingPower` if the user’s token has zero voting power.
* Reverts with `NotManagedNft` if the target is not recognized as a managed NFT.
*/
function onAttachToManagedNFT(uint256 tokenId_, uint256 managedTokenId_) external returns (uint256);
/**
* @notice Detaches a veNFT from a managed NFT.
* @param tokenId_ The ID of the user’s veNFT being detached.
* @param managedTokenId_ The ID of the managed NFT from which it’s being detached.
* @param newBalance_ The new locked balance the veNFT will hold after detachment.
* @dev Reverts with `AccessDenied` if caller is not the managed NFT manager.
* Reverts with `NotManagedNft` if the target is not recognized as a managed NFT.
*/
function onDettachFromManagedNFT(uint256 tokenId_, uint256 managedTokenId_, uint256 newBalance_) external;
/**
* @notice Burns a veLUTE NFT to reclaim the underlying LUTE tokens for use in bribes.
* @dev Must be called by `customBribeRewardRouter`.
* The token must not be permanently locked or attached.
* Also resets any votes before burning.
* Emits a {BurnToBribes} event on successful burn.
* @param tokenId_ The ID of the veLUTE NFT to burn.
*/
function burnToBribes(uint256 tokenId_) external;
}
contracts/integration/interfaces/IUgradeCall.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IUpgradeCall {
function upgradeCall() external;
}
@openzeppelin/contracts/utils/introspection/ERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
contracts/dexV2/PairFees.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IPairFactory} from "./interfaces/IPairFactory.sol";
// 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 PairFees {
address internal immutable pair; // The pair it is bonded to
address internal immutable token0; // token0 of pair, saved localy and statically for gas optimization
address internal immutable token1; // Token1 of pair, saved localy and statically for gas optimization
address internal immutable factory; // The pair factory
constructor(address _factory, address _token0, address _token1) {
pair = msg.sender;
token0 = _token0;
token1 = _token1;
factory = _factory;
}
function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
// Allow the pair to transfer fees to users
function claimFeesFor(address recipient, uint amount0, uint amount1) external {
require(msg.sender == pair);
if (amount0 > 0) _safeTransfer(token0, recipient, amount0);
if (amount1 > 0) _safeTransfer(token1, recipient, amount1);
}
}
@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4 <0.9.0;
import '../interfaces/pool/IAlgebraPoolErrors.sol';
/// @title Contains logic and constants for interacting with the plugin through hooks
/// @dev Allows pool to check which hooks are enabled, as well as control the return selector
library Plugins {
function hasFlag(uint8 pluginConfig, uint256 flag) internal pure returns (bool res) {
assembly {
res := gt(and(pluginConfig, flag), 0)
}
}
function shouldReturn(bytes4 selector, bytes4 expectedSelector) internal pure {
if (selector != expectedSelector) revert IAlgebraPoolErrors.invalidHookResponse(expectedSelector);
}
uint256 internal constant BEFORE_SWAP_FLAG = 1;
uint256 internal constant AFTER_SWAP_FLAG = 1 << 1;
uint256 internal constant BEFORE_POSITION_MODIFY_FLAG = 1 << 2;
uint256 internal constant AFTER_POSITION_MODIFY_FLAG = 1 << 3;
uint256 internal constant BEFORE_FLASH_FLAG = 1 << 4;
uint256 internal constant AFTER_FLASH_FLAG = 1 << 5;
uint256 internal constant AFTER_INIT_FLAG = 1 << 6;
uint256 internal constant DYNAMIC_FEE = 1 << 7;
}
@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol)
pragma solidity ^0.8.0;
import "../ERC721.sol";
import "../../../utils/Context.sol";
/**
* @title ERC721 Burnable Token
* @dev ERC721 Token that can be burned (destroyed).
*/
abstract contract ERC721Burnable is Context, ERC721 {
/**
* @dev Burns `tokenId`. See {ERC721-_burn}.
*
* Requirements:
*
* - The caller must own `tokenId` or be an approved operator.
*/
function burn(uint256 tokenId) public virtual {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_burn(tokenId);
}
}
@openzeppelin/contracts/utils/StorageSlot.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.0;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
* _Available since v4.9 for `string`, `bytes`._
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}
contracts/mocks/GaugeMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract GaugeMock {
address public token;
mapping(address => uint256) public mock_reward;
constructor(address token_) {
token = token_;
}
function mock__setupReward(address target_, uint256 amount_) external {
mock_reward[target_] = amount_;
}
function getReward(address target_) external {
IERC20(token).transfer(target_, mock_reward[target_]);
}
}
contracts/lute/BaseManagedNFTStrategyUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IVoter} from "../core/interfaces/IVoter.sol";
import {IVotingEscrow} from "../core/interfaces/IVotingEscrow.sol";
import {IManagedNFTManager} from "./interfaces/IManagedNFTManager.sol";
import {IManagedNFTStrategy} from "./interfaces/IManagedNFTStrategy.sol";
import {UpgradeCall} from "../integration/UpgradeCall.sol";
/**
* @title Base Managed NFT Strategy Upgradeable
* @dev Abstract base contract for strategies managing NFTs with voting and reward capabilities.
* This contract serves as a foundation for specific managed NFT strategies, incorporating initializable patterns for upgradeability.
*/
abstract contract BaseManagedNFTStrategyUpgradeable is IManagedNFTStrategy, Initializable, UpgradeCall {
/// @notice The name of the strategy for identification purposes.
string public override name;
/// @notice The description of the strategy for identification purposes.
string public override description;
/// @notice The creator of the strategy for identification purposes.
string public override creator;
/// @notice The address of the managed NFT manager that coordinates the overall strategy and access controls.
address public override managedNFTManager;
/// @notice The specific token ID of the NFT being managed under this strategy.
uint256 public override managedTokenId;
/// @notice The address of the voting escrow contract, which locks governance tokens to enable voting power.
address public override votingEscrow;
/// @notice The address of the voter contract, which handles governance actions and reward claims.
address public override voter;
/// @notice Error thrown when an unauthorized user attempts to perform an action reserved for specific roles.
error AccessDenied();
/// @notice Error thrown when attempting to attach a token ID that is either incorrect or already in use.
error IncorrectManagedTokenId();
/// @notice Error thrown when attempting to attach a token ID that has already been attached to another strategy.
error AlreadyAttached();
error AddressZero();
/// @dev Ensures that only the current managed NFT manager contract can call certain functions.
modifier onlyManagedNFTManager() {
if (managedNFTManager != msg.sender) {
revert AccessDenied();
}
_;
}
/// @dev Ensures that only administrators defined in the managed NFT manager can perform certain actions.
modifier onlyAdmin() {
if (!IManagedNFTManager(managedNFTManager).isAdmin(msg.sender)) {
revert AccessDenied();
}
_;
}
/// @dev Ensures that only authorized users, as determined by the managed NFT manager, can call certain functions.
modifier onlyAuthorized() {
if (!IManagedNFTManager(managedNFTManager).isAuthorized(managedTokenId, msg.sender)) {
revert AccessDenied();
}
_;
}
/**
* @dev Initializes the contract, setting up necessary state variables.
* This initialization setup prevents further initialization and ensures proper governance setup.
* @param managedNFTManager_ Address of the managed NFT manager
* @param name_ Descriptive name of the managed NFT strategy
*/
function __BaseManagedNFTStrategy__init(
address managedNFTManager_,
string memory name_
) internal onlyInitializing {
_checkAddressZero(managedNFTManager_);
managedNFTManager = managedNFTManager_;
votingEscrow = IManagedNFTManager(managedNFTManager_).votingEscrow();
voter = IManagedNFTManager(managedNFTManager_).voter();
name = name_;
}
/**
* @notice Attaches a specific managed NFT to this strategy, setting up necessary governance or reward mechanisms.
* @dev This function can only be called by administrators. It sets the `managedTokenId` and ensures that the token is
* valid and owned by this contract. Emits an `AttachedManagedNFT` event upon successful attachment.
* @param managedTokenId_ The token ID of the NFT to be managed by this strategy.
* throws AlreadyAttached if the strategy is already attached to a managed NFT.
* throws IncorrectManagedTokenId if the provided token ID is not managed or not owned by this contract.
*/
function attachManagedNFT(uint256 managedTokenId_) external onlyAdmin {
if (managedTokenId != 0) {
revert AlreadyAttached();
}
if (
!IManagedNFTManager(managedNFTManager).isManagedNFT(managedTokenId_) ||
IVotingEscrow(votingEscrow).ownerOf(managedTokenId_) != address(this)
) {
revert IncorrectManagedTokenId();
}
managedTokenId = managedTokenId_;
emit AttachedManagedNFT(managedTokenId_);
}
/**
* @notice Allows administrative updating of the strategy's name for clarity or rebranding purposes.
* @dev Emits the SetName event upon successful update. This function can only be called by administrators.
*
* @param name_ The new name to set for the strategy, reflecting either its purpose or current operational focus.
*/
function setName(string calldata name_) external onlyAdmin {
name = name_;
emit SetName(name_);
}
/**
* @notice Allows administrative updating of the strategy's creator name for clarity or rebranding purposes.
* @dev Emits the SetCreator event upon successful update. This function can only be called by administrators.
*
* @param creator_ The new creator to set for the strategy, reflecting either its purpose or current operational focus.
*/
function setCreator(string calldata creator_) external onlyAdmin {
creator = creator_;
emit SetCreator(creator_);
}
/**
* @notice Allows administrative updating of the strategy's description.
* @dev Emits the SetDescription event upon successful update. This function can only be called by administrators.
*
* @param description_ The new description to set for the strategy, reflecting either its purpose or current operational focus.
*/
function setDescription(string calldata description_) external onlyAdmin {
description = description_;
emit SetDescription(description_);
}
/**
* @notice Casts votes based on the strategy's parameters.
* @param poolVote_ Array of pool addresses to vote for.
* @param weights_ Array of weights corresponding to each pool address.
*/
function vote(address[] calldata poolVote_, uint256[] calldata weights_) external onlyAuthorized {
IVoter(voter).vote(managedTokenId, poolVote_, weights_);
}
/**
* @notice Claims rewards from the specified gauges.
* @param gauges_ Array of gauge addresses from which to claim rewards.
*/
function claimRewards(address[] calldata gauges_) external {
IVoter(voter).claimRewards(gauges_);
}
/**
* @notice Claims bribes for specific tokens from specified bribe addresses.
* @param bribes_ Array of bribe addresses.
* @param tokens_ Array of arrays of token addresses corresponding to each bribe address.
*/
function claimBribes(address[] calldata bribes_, address[][] calldata tokens_) public {
IVoter(voter).claimBribes(bribes_, tokens_, managedTokenId);
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts/utils/Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
contracts/fees/FeesVaultProxy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import {IFeesVaultFactory} from "./interfaces/IFeesVaultFactory.sol";
contract FeesVaultProxy {
address private immutable feesVaultFactory;
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
constructor() {
feesVaultFactory = msg.sender;
}
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
function _setImplementation(address newImplementation) private {
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
fallback() external payable {
address impl = IFeesVaultFactory(feesVaultFactory).feesVaultImplementation();
require(impl != address(0));
//Just for etherscan compatibility
if (impl != _getImplementation() && msg.sender != (address(0))) {
_setImplementation(impl);
}
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
}
contracts/dexV2/PairFactoryUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {IPairFactory} from "./interfaces/IPairFactory.sol";
import {IPair} from "./interfaces/IPair.sol";
import {IFeesVaultFactory} from "../fees/interfaces/IFeesVaultFactory.sol";
import {ICustomVolatileDynamicFee} from "./interfaces/ICustomVolatileDynamicFee.sol";
contract PairFactoryUpgradeable is IPairFactory, AccessControlUpgradeable {
bytes32 public constant override PAIRS_ADMINISTRATOR_ROLE = keccak256("PAIRS_ADMINISTRATOR");
bytes32 public constant override FEES_MANAGER_ROLE = keccak256("FEES_MANAGER");
bytes32 public constant override PAIRS_CREATOR_ROLE = keccak256("PAIRS_CREATOR");
uint256 public constant MAX_FEE = 500; // 5%
uint256 public constant PRECISION = 10000; // 100%
address public override implementation;
bool public override isPaused;
bool public override isPublicPoolCreationMode;
uint256 public protocolFee;
uint256 public stableFee;
uint256 public volatileFee;
address public communityVaultFactory;
address[] public allPairs;
mapping(address => mapping(address => mapping(bool => address))) public getPair;
mapping(address => bool) public isPair; // simplified check if its a pair, given that `stable` flag might not be available in peripherals
mapping(address => uint256) internal _customFee;
mapping(address => uint256) internal _customProtocolFee;
mapping(address => address) internal _customVolatileDynamicFeeModule;
error AddressZero();
constructor() {
_disableInitializers();
}
function initialize(address implementation_, address communityVaultFactory_) external initializer {
_checkAddressZero(implementation_);
_checkAddressZero(communityVaultFactory_);
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
stableFee = 4; // 0.04%
volatileFee = 18; // 0.18%
protocolFee = 10000; // 100% of stable/volatileFee to communit vaults
implementation = implementation_;
communityVaultFactory = communityVaultFactory_;
}
function upgradePairImplementation(address implementation_) external onlyRole(DEFAULT_ADMIN_ROLE) reinitializer(2) {
implementation = implementation_;
}
function setPause(bool _state) external onlyRole(PAIRS_ADMINISTRATOR_ROLE) {
isPaused = _state;
emit SetPaused(_state);
}
function setCommunityVaultFactory(address communityVaultFactory_) external onlyRole(PAIRS_ADMINISTRATOR_ROLE) {
_checkAddressZero(communityVaultFactory_);
communityVaultFactory = communityVaultFactory_;
emit SetCommunityVaultFactory(communityVaultFactory_);
}
function setIsPublicPoolCreationMode(bool mode_) external onlyRole(PAIRS_ADMINISTRATOR_ROLE) {
isPublicPoolCreationMode = mode_;
emit SetIsPublicPoolCreationMode(mode_);
}
function setProtocolFee(uint256 _newFee) external onlyRole(FEES_MANAGER_ROLE) {
if (_newFee > PRECISION) {
revert IncorrcectFee();
}
protocolFee = _newFee;
emit SetProtocolFee(_newFee);
}
function setCustomProtocolFee(address _pair, uint256 _newFee) external onlyRole(FEES_MANAGER_ROLE) {
_checkFeeAndPair(_pair, _newFee, PRECISION);
_customProtocolFee[_pair] = _newFee;
emit SetCustomProtocolFee(_pair, _newFee);
}
function setCustomVolatileDynamicFeeModule(address pair_, address module_) external onlyRole(DEFAULT_ADMIN_ROLE) {
_checkAddressZero(pair_);
/*
* @Dev Dont check for the exist of a pair, it is allowed to set
* the module for not exist pair's address in the future through the calculation of the address
*/
_customVolatileDynamicFeeModule[pair_] = module_;
emit SetCustomVolatileDynamicFeeModule(pair_, module_);
}
function setCustomFee(address _pair, uint256 _fee) external onlyRole(FEES_MANAGER_ROLE) {
_checkFeeAndPair(_pair, _fee, MAX_FEE);
_customFee[_pair] = _fee;
emit SetCustomFee(_pair, _fee);
}
function setFee(bool _stable, uint256 _fee) external onlyRole(FEES_MANAGER_ROLE) {
if (_fee == 0 || _fee > MAX_FEE) {
revert IncorrcectFee();
}
if (_stable) {
stableFee = _fee;
} else {
volatileFee = _fee;
}
emit SetFee(_stable, _fee);
}
function createPair(address tokenA, address tokenB, bool stable) external virtual override returns (address pair) {
if (!isPublicPoolCreationMode) {
_checkRole(PAIRS_CREATOR_ROLE);
}
if (tokenA == tokenB) {
revert IdenticalAddress();
}
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
if (token0 == address(0)) {
revert AddressZero();
}
if (getPair[token0][token1][stable] != address(0)) {
revert PairExist();
}
pair = Clones.cloneDeterministic(implementation, keccak256(abi.encodePacked(token0, token1, stable)));
address feesVaultForPool = IFeesVaultFactory(communityVaultFactory).createVaultForPool(pair);
IPair(pair).initialize(token0, token1, stable, feesVaultForPool);
getPair[token0][token1][stable] = pair;
getPair[token1][token0][stable] = pair; // populate mapping in the reverse direction
allPairs.push(pair);
isPair[pair] = true;
emit PairCreated(token0, token1, stable, pair, allPairs.length);
}
function hasRole(bytes32 role, address user) public view override(AccessControlUpgradeable, IPairFactory) returns (bool) {
return super.hasRole(role, user);
}
function getCustomVolatileDynamicFeeModule(address pair_) external view virtual override returns (address) {
return _customVolatileDynamicFeeModule[pair_];
}
// Stub functions for future improvments
function getHookTarget(address /*pair_*/) external view virtual override returns (address) {
return address(0);
}
function getFee(address pair_, bool stable_) external view virtual override returns (uint256) {
uint256 fee = _customFee[pair_];
if (fee != 0) {
return fee;
}
if (stable_) {
return stableFee;
}
address cDFM = _customVolatileDynamicFeeModule[pair_];
if (cDFM == address(0)) {
return volatileFee;
}
if (ICustomVolatileDynamicFee(cDFM).isEnable()) {
(bool success, uint256 dynamicFee) = ICustomVolatileDynamicFee(cDFM).getFee(pair_);
if (success) {
return dynamicFee;
}
}
return volatileFee;
}
function getProtocolFee(address pair_) external view virtual override returns (uint256) {
uint256 fee = _customProtocolFee[pair_];
if (fee != 0) {
return fee;
}
return protocolFee;
}
function allPairsLength() external view virtual override returns (uint) {
return allPairs.length;
}
function pairs() external view virtual override returns (address[] memory) {
return allPairs;
}
function _checkFeeAndPair(address pair_, uint256 fee_, uint256 upperLimit_) internal view {
if (fee_ > upperLimit_) {
revert IncorrcectFee();
}
if (!isPair[pair_]) {
revert IncorrectPair();
}
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
contracts/lute/CompoundVeLUTEManagedNFTStrategyUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {BaseManagedNFTStrategyUpgradeable, IManagedNFTManager} from "./BaseManagedNFTStrategyUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import {IERC721ReceiverUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol";
import {IERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol";
import {IVotingEscrow} from "../core/interfaces/IVotingEscrow.sol";
import {ISingelTokenVirtualRewarder} from "./interfaces/ISingelTokenVirtualRewarder.sol";
import {ICompoundVeLUTEManagedNFTStrategy} from "./interfaces/ICompoundVeLUTEManagedNFTStrategy.sol";
import {IRouterV2PathProvider, SingelTokenBuybackUpgradeable} from "./SingelTokenBuybackUpgradeable.sol";
import {LibStrategyFlags} from "./libraries/LibStrategyFlags.sol";
import {LibStrategyFlags} from "./libraries/LibStrategyFlags.sol";
/**
* @title Compound VeLUTE Managed NFT Strategy Upgradeable
* @dev Strategy for managing VeLUTE-related actions including compounding rewards and managing stakes.
* Extends the functionality of a base managed NFT strategy to interact with lute tokens.
* @notice This strategy handles the automated compounding of VeLUTE tokens by reinvesting harvested rewards back into VeLUTE.
*/
contract CompoundVeLUTEManagedNFTStrategyUpgradeable is
ICompoundVeLUTEManagedNFTStrategy,
IERC721ReceiverUpgradeable,
BaseManagedNFTStrategyUpgradeable,
SingelTokenBuybackUpgradeable
{
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @dev Reverts when attempting to recover tokens that are critical for the strategy and must not be removed
* (e.g., main lute tokens or locked veNFT).
*/
error IncorrectRecoverToken();
/**
* @dev Reverts if no new rewards were added during a merge operation, i.e., the `locked.amount`
* did not increase after merging veNFTs.
*/
error ZeroCompoundVeNFTsReward();
/**
* @dev Reverts if the caller tries to merge or operate on veNFT IDs that this strategy does not own.
*/
error InvalidVeNFTTokenIds();
/**
* @dev Reverts if `compoundVeNFTsAll()` is called but there are no other veNFTs to merge except the `managedTokenId`.
*/
error NotOtherVeNFTsAvailable();
/**
* @dev Reverts if try merge or recover managed token id
*/
error NotAllowedActionWithManagedTokenId();
/**
* @notice Error thrown when a provided detachment-lock duration exceeds the allowed maximum.
* @param value The duration (in seconds) that was requested to be set.
* @param max The maximum allowed duration (in seconds).
*/
error DetachmentLockDurationTooLong(uint256 value, uint256 max);
/**
* @notice Reverts when a detachment is attempted within the active lock window.
* @param unlockAt Timestamp when the lock ends and detachment becomes allowed.
*/
error DettachLockWindowActive(uint256 unlockAt);
/// @notice The address of the lute ERC20 token contract. Used for depositing to Voting Escrow.
address public override lute;
/// @notice The address of the virtual rewarder contract for distributing additional rewards.
address public override virtualRewarder;
/**
* @notice Optional per-strategy override for the detachment lock duration.
* @dev If set to 0, the strategy uses the manager's {defaultDetachmentLockDuration()}.
*/
uint256 public detachmentLockDuration;
/**
* @notice Hard cap for per-strategy detachment lock override (aligned with manager's 6 days bound).
* @dev Keep in sync with the manager-side maximum.
*/
uint256 internal constant STRATEGY_MAX_DETACH_LOCK_DURATION = 6 days;
/**
* @dev Epoch duration
*/
uint256 internal constant WEEK = 86400 * 7;
/**
* @notice Initializes the contract in an uninitialized state and disables further initializations.
* @dev This constructor is called at the time of contract deployment and uses `_disableInitializers()`
* to ensure the upgradeable contract cannot be initialized twice.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the strategy with the given parameters.
* @dev Ensures addresses are non-zero and sets up references to the `managedNFTManager`,
* the `virtualRewarder`, and the `routerV2PathProvider`. Also fetches the lute token
* from the Voting Escrow.
*
* @param managedNFTManager_ The address of the managed NFT manager contract.
* @param virtualRewarder_ The address of the virtual rewarder contract for extra reward distribution.
* @param routerV2PathProvider_ The address of the router V2 path provider for buyback route management.
* @param name_ The name (identifier) of this strategy, stored in base contracts.
*/
function initialize(
address managedNFTManager_,
address virtualRewarder_,
address routerV2PathProvider_,
string memory name_
) external override initializer {
_checkAddressZero(virtualRewarder_);
__BaseManagedNFTStrategy__init(managedNFTManager_, name_);
__SingelTokenBuyback__init(routerV2PathProvider_);
lute = IVotingEscrow(votingEscrow).token();
virtualRewarder = virtualRewarder_;
}
/**
* @notice Computes the current detachment-lock window and indicates whether detachment is blocked.
* @dev
* - The epoch start is aligned to the beginning of the current week:
* `epochStart = floor(block.timestamp / WEEK) * WEEK`.
* - The effective lock duration is the strategy override `detachmentLockDuration` if non-zero;
* otherwise it falls back to `managedNFTManager.defaultDetachmentLockDuration()`.
* - If the effective duration resolves to zero, the lock is considered disabled for this epoch and
* `lockEnd` equals `epochStart`.
* - Detachment is considered locked while `block.timestamp < lockEnd`.
* @return locked True if detachment is time-locked at the current block timestamp.
* @return epochStart The aligned start timestamp of the current weekly epoch.
* @return lockEnd The timestamp when the lock window ends; equals `epochStart + duration`
* (or `epochStart` when duration is zero).
*/
function dettachLockWindowInfo() public view returns (bool locked, uint256 epochStart, uint256 lockEnd) {
epochStart = (block.timestamp / WEEK) * WEEK;
uint256 duration = detachmentLockDuration;
if (duration == 0) {
duration = IManagedNFTManager(managedNFTManager).defaultDetachmentLockDuration();
}
if (duration == 0) {
return (false, epochStart, epochStart);
}
lockEnd = epochStart + duration;
return (block.timestamp < lockEnd, epochStart, lockEnd);
}
/**
* @notice Attaches an NFT to the strategy and initializes participation in the virtual reward system.
* @dev This function is called when an NFT is attached to this strategy, enabling it to start accumulating rewards.
*
* @param tokenId_ The identifier of the NFT to attach.
* @param userBalance_ The initial balance or stake associated with the NFT at the time of attachment.
*/
function onAttach(uint256 tokenId_, uint256 userBalance_) external override onlyManagedNFTManager {
ISingelTokenVirtualRewarder(virtualRewarder).deposit(tokenId_, userBalance_);
emit OnAttach(tokenId_, userBalance_);
}
/**
* @notice Detaches an NFT from the strategy, withdrawing all associated rewards and balances.
* @dev
* - Enforces the detachment time-lock by querying {dettachLockWindowInfo}. Reverts with
* {DettachLockWindowActive} providing `lockEnd` if detachment is attempted while locked.
* - On success, withdraws the user's balance from the virtual rewarder and harvests accrued rewards.
* - Emits {OnDettach}.
* @param tokenId_ The identifier of the NFT to detach.
* @param userBalance_ The remaining balance or stake associated with the NFT at the time of detachment.
* @return lockedRewards The amount of rewards locked and harvested upon detachment.
*/
function onDettach(uint256 tokenId_, uint256 userBalance_) external override onlyManagedNFTManager returns (uint256 lockedRewards) {
(bool locked, , uint256 lockEnd) = dettachLockWindowInfo();
if (locked) {
revert DettachLockWindowActive(lockEnd);
}
ISingelTokenVirtualRewarder virtualRewarderCache = ISingelTokenVirtualRewarder(virtualRewarder);
virtualRewarderCache.withdraw(tokenId_, userBalance_);
lockedRewards = virtualRewarderCache.harvest(tokenId_);
emit OnDettach(tokenId_, userBalance_, lockedRewards);
}
/**
* @notice Retrieves the total amount of locked rewards available for a specific NFT based on its tokenId.
* @param tokenId_ The identifier of the NFT to query.
* @return The total amount of locked rewards for the specified NFT.
*/
function getLockedRewardsBalance(uint256 tokenId_) external view returns (uint256) {
return ISingelTokenVirtualRewarder(virtualRewarder).calculateAvailableRewardsAmount(tokenId_);
}
/**
* @notice Retrieves the balance or stake associated with a specific NFT.
* @param tokenId_ The identifier of the NFT to query.
* @return The balance of the specified NFT.
*/
function balanceOf(uint256 tokenId_) external view returns (uint256) {
return ISingelTokenVirtualRewarder(virtualRewarder).balanceOf(tokenId_);
}
/**
* @notice Retrieves the total supply of stakes managed by the strategy.
* @return The total supply of stakes.
*/
function totalSupply() external view returns (uint256) {
return ISingelTokenVirtualRewarder(virtualRewarder).totalSupply();
}
/**
* @notice Merges (compounds) all veNFTs owned by this strategy except the managed one (`managedTokenId`).
* @dev Checks if there is more than one veNFT owned. If only the managed one is present,
* it reverts with `NotOtherVeNFTsAvailable()`. This method is public and restricted
* by strategy flags/permissions.
*/
function compoundVeNFTsAll() external {
_requirePermisisonIfNotSetupFlag(LibStrategyFlags.IGNORE_RESTRICTIONS_ON_PUBLIC_VE_NFT_COMPOUND);
IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
uint256 totalBalance = votingEscrowCache.balanceOf(address(this));
if (totalBalance <= 1) {
revert NotOtherVeNFTsAvailable();
}
uint256 managedTokenIdCache = managedTokenId;
uint256 length = totalBalance - 1;
uint256 count;
uint256[] memory tokenIds = new uint256[](length);
for (uint256 i; i < totalBalance; ) {
uint256 tokenId = IERC721EnumerableUpgradeable(address(votingEscrowCache)).tokenOfOwnerByIndex(address(this), i);
if (tokenId != managedTokenIdCache) {
tokenIds[count] = tokenId;
++count;
}
unchecked {
++i;
}
}
_compoundVeNFTs(tokenIds);
}
/**
* @notice Merges (compounds) the specified list of veNFT IDs into the managed veNFT.
* @dev Ensures that each veNFT ID is actually owned by this contract. This method is public
* and restricted by strategy flags/permissions.
* @param tokenIds_ The list of veNFT IDs to be merged.
*/
function compoundVeNFTs(uint256[] calldata tokenIds_) external {
if (tokenIds_.length == 0) {
revert ZeroCompoundVeNFTsReward();
}
_requirePermisisonIfNotSetupFlag(LibStrategyFlags.IGNORE_RESTRICTIONS_ON_PUBLIC_VE_NFT_COMPOUND);
IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
uint256 length = tokenIds_.length;
for (uint256 i; i < length; ) {
if (votingEscrowCache.ownerOf(tokenIds_[i]) != address(this)) {
revert InvalidVeNFTTokenIds();
}
unchecked {
++i;
}
}
_compoundVeNFTs(tokenIds_);
}
/**
* @notice Compounds lute tokens by depositing the current lute balance into the managed veNFT.
* @dev This operation locks more lute into `managedTokenId` in the Voting Escrow,
* and then notifies the `virtualRewarder` about the updated reward amount.
* Restricted by strategy flags/permissions.
*/
function compound() external {
_requirePermisisonIfNotSetupFlag(LibStrategyFlags.IGNORE_RESTRICTIONS_ON_PUBLIC_ERC20_COMPOUND);
IERC20Upgradeable luteCache = IERC20Upgradeable(lute);
uint256 currentBalance = luteCache.balanceOf(address(this));
if (currentBalance > 0) {
address votingEscrowCache = votingEscrow;
luteCache.forceApprove(votingEscrowCache, currentBalance);
IVotingEscrow(votingEscrowCache).depositFor(managedTokenId, currentBalance, false, false);
ISingelTokenVirtualRewarder(virtualRewarder).notifyRewardAmount(currentBalance);
emit Compound(msg.sender, currentBalance);
}
}
/**
* @notice Claims bribes for the current strategy and recovers specified ERC20 tokens to a recipient.
* @dev This function allows the strategy to claim bribes from specified contracts and transfer
* non-strategic ERC20 tokens back to the designated recipient in a single transaction.
* @param bribes_ The list of addresses representing bribe contracts from which to claim rewards.
* @param tokens_ A nested array where each entry corresponds to a list of token addresses to claim from the respective bribe contract.
* @param recipient_ The address to which recovered tokens should be sent.
* @param tokensToRecover_ The list of ERC20 token addresses to be recovered and transferred to the recipient.
*
* Emits:
* - Emits `Erc20Recover` for each recovered token.
*/
function claimBribesWithERC20Recover(
address[] calldata bribes_,
address[][] calldata tokens_,
address recipient_,
address[] calldata tokensToRecover_
) external {
_checkBuybackSwapPermissions();
if (bribes_.length > 0) {
claimBribes(bribes_, tokens_);
}
for (uint256 i; i < tokensToRecover_.length; ) {
_erc20Recover(tokensToRecover_[i], recipient_);
unchecked {
i++;
}
}
}
/**
* @notice Claims bribes from multiple addresses and recovers both specified ERC20 tokens and specified veNFTs to the given recipient.
* @dev Extends `claimBribesWithERC20Recover` by also recovering veNFTs if `veNftTokenIdsToRecover_` is non-empty.
* Protected by `_checkBuybackSwapPermissions()`.
* @param bribes_ Array of addresses from which to claim bribes.
* @param tokens_ Nested array of token addresses corresponding to each bribe address.
* @param recipient_ The address to which recovered tokens/NFTs are sent.
* @param tokensToRecover_ The list of ERC20 tokens to be recovered and transferred to `recipient_`.
* @param veNftTokenIdsToRecover_ The list of veNFT IDs to be recovered and transferred to `recipient_`.
*/
function claimBribesWithTokensRecover(
address[] calldata bribes_,
address[][] calldata tokens_,
address recipient_,
address[] calldata tokensToRecover_,
uint256[] calldata veNftTokenIdsToRecover_
) external {
_checkBuybackSwapPermissions();
if (bribes_.length > 0) {
claimBribes(bribes_, tokens_);
}
for (uint256 i; i < tokensToRecover_.length; ) {
_erc20Recover(tokensToRecover_[i], recipient_);
unchecked {
i++;
}
}
if (veNftTokenIdsToRecover_.length > 0) {
_erc721Recover(votingEscrow, recipient_, veNftTokenIdsToRecover_);
}
}
/**
* @notice Set the per-strategy detachment lock duration.
* @dev
* - Set to 0 to fall back to the manager default.
* - Must not exceed {STRATEGY_MAX_DETACH_LOCK_DURATION}.
* - Emits {SetDetachmentLockDuration}.
* @param newDuration_ New duration in seconds (0 = use manager default).
*/
function setDetachmentLockDuration(uint256 newDuration_) external onlyAdmin {
if (newDuration_ > STRATEGY_MAX_DETACH_LOCK_DURATION) {
revert DetachmentLockDurationTooLong(newDuration_, STRATEGY_MAX_DETACH_LOCK_DURATION);
}
uint256 old = detachmentLockDuration;
detachmentLockDuration = newDuration_;
emit SetDetachmentLockDuration(old, newDuration_);
}
/**
* @notice Sets a new address for the Router V2 Path Provider.
* @dev Accessible only by admins, this function updates the address used for determining swap routes in token buyback strategies.
* @param routerV2PathProvider_ The new Router V2 Path Provider address.
*/
function setRouterV2PathProvider(address routerV2PathProvider_) external virtual onlyAdmin {
_checkAddressZero(routerV2PathProvider_);
emit SetRouterV2PathProvider(routerV2PathProvider, routerV2PathProvider_);
routerV2PathProvider = routerV2PathProvider_;
}
/**
* @notice Recovers ERC20 tokens accidentally sent to this contract, excluding the managed token (lute).
* @dev Allows the admin to recover non-strategic ERC20 tokens sent to the contract.
* @param token_ The address of the token to recover.
* @param recipient_ The address where the recovered tokens should be sent.
*/
function erc20Recover(address token_, address recipient_) external {
_checkBuybackSwapPermissions();
_erc20Recover(token_, recipient_);
}
/**
* @notice Recovers specified NFT tokens from this contract to a given recipient.
* @param token_ The NFT contract address (e.g. `votingEscrow` or other ERC721).
* @param recipient_ The address receiving the recovered NFTs.
* @param tokenIds_ The list of NFT IDs to transfer.
*/
function erc721Recover(address token_, address recipient_, uint256[] calldata tokenIds_) external {
_checkBuybackSwapPermissions();
_erc721Recover(token_, recipient_, tokenIds_);
}
/**
* @notice Implementation of the ERC721 Receiver interface.
* @dev Allows this contract to safely receive ERC721 tokens.
* @return Returns a specific selector to confirm the receipt of an NFT.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
/**
* @notice Internal logic for recovering an array of NFTs, restricted by flags for critical tokens.
* @dev If flag `IGNORE_RESTRICTIONS_ON_RECOVER_VE_NFT_TOKENS` is not set,
* recovering `votingEscrow` tokens is disallowed.
* @param token_ The NFT contract address.
* @param recipient_ The address receiving the NFTs.
* @param tokenIds_ The list of NFTs to transfer.
*/
function _erc721Recover(address token_, address recipient_, uint256[] calldata tokenIds_) internal {
if (!_hasFlag(LibStrategyFlags.IGNORE_RESTRICTIONS_ON_RECOVER_VE_NFT_TOKENS)) {
if (token_ == address(votingEscrow)) {
revert IncorrectRecoverToken();
}
}
uint256 managedTokenIdCache;
if (votingEscrow == token_) {
managedTokenIdCache = managedTokenId;
}
for (uint256 i; i < tokenIds_.length; ) {
uint256 tokenId = tokenIds_[i];
if (managedTokenIdCache > 0 && tokenId == managedTokenIdCache) {
revert NotAllowedActionWithManagedTokenId();
}
IERC721Upgradeable(token_).safeTransferFrom(address(this), recipient_, tokenId, "");
unchecked {
i++;
}
}
emit Erc721Recover(msg.sender, recipient_, token_, tokenIds_);
}
/**
* @dev Recovers the full balance of a specified ERC20 token held by this contract and
* sends it to `recipient_`. Prevents recovery of `lute` or tokens used in buyback routes
* unless the relevant flags are set.
* @param token_ The ERC20 token address to recover.
* @param recipient_ The address receiving the tokens.
*
* Emits:
* - `Erc20Recover` event.
*/
function _erc20Recover(address token_, address recipient_) internal {
if (!_hasFlag(LibStrategyFlags.IGNORE_RESTRICTIONS_ON_RECOVER_TOKENS)) {
if (token_ == address(lute) || IRouterV2PathProvider(routerV2PathProvider).isAllowedTokenInInputRoutes(token_)) {
revert IncorrectRecoverToken();
}
}
uint256 amount = IERC20Upgradeable(token_).balanceOf(address(this));
if (amount > 0) {
IERC20Upgradeable(token_).safeTransfer(recipient_, amount);
emit Erc20Recover(msg.sender, recipient_, token_, amount);
}
}
/**
* @notice Performs the merging (compounding) of veNFT tokens into the managed token.
* @dev This internal function:
* 1. Calculates the current locked amount in `managedTokenId`.
* 2. Merges each veNFT from `tokenIds_` into the `managedTokenId`, ensuring none are the same ID.
* 3. Measures the increased locked balance post-merge.
* 4. If `compoundRewards` is zero, reverts with `ZeroCompoundVeNFTsReward()`.
* 5. Notifies the `virtualRewarder` about the new locked amount and emits a `Compound` event.
* @param tokenIds_ The array of veNFT IDs to merge.
*/
function _compoundVeNFTs(uint256[] memory tokenIds_) internal {
IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
uint256 managedTokenIdCache = managedTokenId;
uint256 balanceBefore = uint256(int256(votingEscrowCache.getNftState(managedTokenIdCache).locked.amount));
uint256 length = tokenIds_.length;
for (uint256 i; i < length; ) {
uint256 tokenId = tokenIds_[i];
if (tokenId == managedTokenIdCache) {
revert NotAllowedActionWithManagedTokenId();
}
votingEscrowCache.merge(tokenId, managedTokenIdCache);
unchecked {
i++;
}
}
uint256 balanceAfter = uint256(int256(votingEscrowCache.getNftState(managedTokenIdCache).locked.amount));
uint256 compoundRewards = balanceAfter - balanceBefore;
ISingelTokenVirtualRewarder(virtualRewarder).notifyRewardAmount(compoundRewards);
emit Compound(msg.sender, compoundRewards);
}
/**
* @dev Internal function to enforce permissions or rules
*/
function _checkBuybackSwapPermissions() internal view virtual override {
IManagedNFTManager managedNFTManagerCache = IManagedNFTManager(managedNFTManager);
if (managedNFTManagerCache.isAdmin(msg.sender) || managedNFTManagerCache.isAuthorized(managedTokenId, msg.sender)) {
return;
}
revert AccessDenied();
}
/**
* @dev Internal helper to fetch the target token for buybacks.
* @return The address of the buyback target token.
*/
function _getBuybackTargetToken() internal view virtual override returns (address) {
return lute;
}
/**
* @dev Checks that the provided address is not the zero address. Reverts if it is.
* Overridden from multiple parents to unify the zero-address check logic.
* @param addr_ The address to check.
*/
function _checkAddressZero(address addr_) internal pure override(BaseManagedNFTStrategyUpgradeable, SingelTokenBuybackUpgradeable) {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev Ensures only authorized or flagged calls can proceed. If the provided flag is NOT set,
* it calls `_checkBuybackSwapPermissions()` to validate authorization.
* @param flag_ A strategy flag from `LibStrategyFlags` to check.
*/
function _requirePermisisonIfNotSetupFlag(uint256 flag_) internal view {
if (!_hasFlag(flag_)) {
_checkBuybackSwapPermissions();
}
}
/**
* @dev Checks whether a specific strategy flag is set in the manager.
* @param flag_ The flag to check from `LibStrategyFlags`.
* @return True if the flag is set, false otherwise.
*/
function _hasFlag(uint256 flag_) internal view returns (bool) {
return LibStrategyFlags.hasFlag(IManagedNFTManager(managedNFTManager).getStrategyFlags(address(this)), flag_);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts-upgradeable/interfaces/IERC5267Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol)
pragma solidity ^0.8.0;
interface IERC5267Upgradeable {
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}
contracts/lute/interfaces/IPairQuote.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IPairQuote {
function quote(address tokenIn, uint amountIn, uint granularity) external view returns (uint amountOut);
}
@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20Upgradeable.sol";
import "./extensions/IERC20MetadataUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[45] private __gap;
}
@openzeppelin/contracts/proxy/Proxy.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
pragma solidity ^0.8.0;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive() external payable virtual {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overridden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {}
}
contracts/core/VeBoostUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {EnumerableSetUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
import {IVeBoost} from "./interfaces/IVeBoost.sol";
import {IPriceProvider} from "../integration/interfaces/IPriceProvider.sol";
/**
* @title VeBoostUpgradeable
* @dev Implements boosting functionality within the Lute ecosystem, allowing users to receive boosts based on locked LUTE tokens.
*/
contract VeBoostUpgradeable is IVeBoost, Ownable2StepUpgradeable {
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @dev Return precision for boost calculations
*/
uint256 internal constant _PRECISION = 10_000;
/**
* @dev Return precision for token calcualtions
*/
uint256 internal constant _LUTE_PREICSION = 1e18;
/**
* @dev Return maximum locking time in seconds (about 6 months)
*/
uint256 internal constant _MAXTIME = 182 * 86400;
/**
* @dev Return address of LUTE token
*/
address public lute;
/**
* @dev Return address of the Voting Escrow contract for Lute
*/
address public votingEscrow;
/**
* @dev Return address of the price provider contract for USD/LUTE conversion
*/
address public priceProvider;
/**
* @dev Return minimum USD amount required for a boost to be considered
*/
uint256 public minUSDAmount;
/**
* @dev Return minimum locking time required for a boost
*/
uint256 internal _minLockedTime;
/**
* @dev Return percentage of LUTE boost
*/
uint256 internal _boostLUTEPercentage;
/**
* @dev Stora set of addresses for reward tokens
*/
EnumerableSetUpgradeable.AddressSet internal _rewardTokens;
error AddressZero();
/**
* @dev Initializes the contract by disabling the initializer of the inherited upgradeable contract.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the VeBoost contract with necessary addresses and settings.
* @param lute_ Address of the Lute token.
* @param votingEscrow_ Address of the Voting Escrow contract.
* @param priceProvider_ Address of the price provider contract.
* Initializes contract state and sets up necessary approvals.
*/
function initialize(address lute_, address votingEscrow_, address priceProvider_) external initializer {
_checkAddressZero(lute_);
_checkAddressZero(votingEscrow_);
_checkAddressZero(priceProvider_);
__Ownable2Step_init();
lute = lute_;
votingEscrow = votingEscrow_;
priceProvider = priceProvider_;
minUSDAmount = 10e18; // Initialize minimum USD amount for boost eligibility to $10
_minLockedTime = 182 * 86400; // Initialize minimum locked time to approximately 6 months
_boostLUTEPercentage = 1_000; // Initialize LUTE boost percentage to 10%
IERC20Upgradeable(lute).forceApprove(votingEscrow_, type(uint256).max);
}
/**
* @notice Sets a new address for the LUTE to USD price provider.
* @param priceProvider_ The address of the new price provider.
* Only the contract owner can call this function.
*/
function setPriceProvider(address priceProvider_) external onlyOwner {
_checkAddressZero(priceProvider_);
priceProvider = priceProvider_;
emit PriceProvider(priceProvider_);
}
/**
* @notice Sets a new boost percentage for LUTE tokens.
* @param boostLUTEPercentage_ The new boost percentage in basis points.
* Only the contract owner can call this function.
*/
function setLUTEBoostPercentage(uint256 boostLUTEPercentage_) external onlyOwner {
_boostLUTEPercentage = boostLUTEPercentage_;
emit LUTEBoostPercentage(boostLUTEPercentage_);
}
/**
* @notice Sets a new minimum USD amount required to qualify for a boost.
* @param minUSDAmount_ The new minimum USD amount in the 18 decimals
* Only the contract owner can call this function.
*/
function setMinUSDAmount(uint256 minUSDAmount_) external onlyOwner {
minUSDAmount = minUSDAmount_;
emit MinUSDAmount(minUSDAmount_);
}
/**
* @notice Sets a new minimum locked time required to qualify for a boost.
* @param minLockedTime_ The new minimum locked time in seconds.
* Only the contract owner can call this function. The time cannot exceed the predefined maximum.
*/
function setMinLockedTime(uint256 minLockedTime_) external onlyOwner {
if (minLockedTime_ > _MAXTIME) {
revert InvalidMinLockedTime();
}
_minLockedTime = minLockedTime_;
emit MinLockedTime(minLockedTime_);
}
/**
* @dev Allows owner to recover tokens
* @param token_ Address of the token to recover.
* @param recoverAmount_ Amount of the token to recover.
*/
function recoverTokens(address token_, uint256 recoverAmount_) external onlyOwner {
IERC20Upgradeable(token_).safeTransfer(msg.sender, recoverAmount_);
emit RecoverToken(token_, recoverAmount_);
}
/**
* @dev Adds a new reward token to the list of tokens users can receive as boosts.
* Can only be called by the contract owner. Emits an `AddRewardToken` event upon success.
* @param newRewardToken_ The address of the token to be added as a new reward token.
*/
function addRewardToken(address newRewardToken_) external onlyOwner {
_checkAddressZero(newRewardToken_);
if (newRewardToken_ == lute) {
revert RewardTokenExist();
}
if (!_rewardTokens.add(newRewardToken_)) {
revert RewardTokenExist();
}
emit AddRewardToken(newRewardToken_);
}
/**
* @dev Removes a reward token from the list of tokens users can receive as boosts.
* Can only be called by the contract owner. Emits a `RemoveRewardToken` event upon success.
* @param rewardToken_ The address of the reward token to be removed.
*/
function removeRewardToken(address rewardToken_) external onlyOwner {
if (!_rewardTokens.remove(rewardToken_)) {
revert RewardTokenNotExist();
}
emit RemoveRewardToken(rewardToken_);
}
/**
* @notice Distributes boost rewards to the token owner before executing the LUTE boost payment.
* Requires the caller to be the Voting Escrow contract.
* @dev This function calculates and distributes reward tokens proportionally based on the paid LUTE boost amount.
* It verifies that the call is made by the Voting Escrow contract, checks if the paid boost amount is within allowed limits,
* and then proceeds to distribute reward tokens to the boost recipient. The distribution is proportional to the amount of LUTE paid
* for the boost relative to the total LUTE balance of this contract, ensuring fairness in reward distribution.
*
* @param tokenOwner_ The address of the owner receiving the boost rewards. This is typically the holder of locked LUTE tokens.
* @param tokenId_ The ID of the token receiving the boost. This parameter is not used in the current implementation but is required for interface compliance.
* @param depositedLUTEAmount_ The total amount of LUTE tokens deposited by the token owner for the boost. This is used to calculate the eligibility and amount of the boost.
* @param paidBoostLUTEAmount_ The amount of LUTE tokens paid by the token owner to achieve the boost. Rewards are distributed based on this amount.
*
* Reverts with `AccessDenied` if called by any address other than the Voting Escrow contract.
* Reverts with `InvalidBoostAmount` if the paid boost amount exceeds the calculated boost amount or the available boost LUTE amount.
*/
function beforeLUTEBoostPaid(
address tokenOwner_,
uint256 tokenId_,
uint256 depositedLUTEAmount_,
uint256 paidBoostLUTEAmount_
) external override {
if (msg.sender != votingEscrow) {
revert AccessDenied();
}
if (paidBoostLUTEAmount_ > calculateBoostLUTEAmount(depositedLUTEAmount_) || paidBoostLUTEAmount_ > getAvailableBoostLUTEAmount()) {
revert InvalidBoostAmount();
}
if (paidBoostLUTEAmount_ > 0) {
uint256 luteBoostToBalanceRation = (paidBoostLUTEAmount_ * _LUTE_PREICSION) / IERC20Upgradeable(lute).balanceOf(address(this));
for (uint256 i; i < _rewardTokens.length(); ) {
IERC20Upgradeable rewardToken = IERC20Upgradeable(_rewardTokens.at(i));
uint256 rewardTokenBoostAmount = (luteBoostToBalanceRation * rewardToken.balanceOf(address(this))) / _LUTE_PREICSION;
if (rewardTokenBoostAmount > 0) {
rewardToken.safeTransfer(tokenOwner_, rewardTokenBoostAmount);
emit RewardSent(address(rewardToken), tokenOwner_, rewardTokenBoostAmount);
}
unchecked {
i++;
}
}
}
}
/**
* @dev Returns an array of addresses for all reward tokens available.
* @return An array of addresses of reward tokens.
*/
function rewardTokens() external view returns (address[] memory) {
return _rewardTokens.values();
}
/**
* @dev Returns the minimum LUTE amount required for receiving a boost.
* @return The minimum amount of LUTE required for a boost.
*/
function getMinLUTEAmountForBoost() external view override returns (uint256) {
return _getMinLUTEAmountForBoost();
}
/**
* @dev Returns the minimum locked time required to qualify for a boost.
* @return The minimum locked time in seconds.
*/
function getMinLockedTimeForBoost() external view override returns (uint256) {
return _minLockedTime;
}
/**
* @dev Returns the current LUTE boost percentage.
* @return The boost percentage.
*/
function getBoostLUTEPercentage() external view returns (uint256) {
return _boostLUTEPercentage;
}
/**
* @dev Returns the available amount of LUTE for boosts, considering both balance and allowance.
* @return The available LUTE amount for boosts.
*/
function getAvailableBoostLUTEAmount() public view override returns (uint256) {
uint256 availableBalance = IERC20Upgradeable(lute).balanceOf(address(this));
uint256 availableAllowance = IERC20Upgradeable(lute).allowance(address(this), votingEscrow);
return availableAllowance > availableBalance ? availableBalance : availableAllowance;
}
/**
* @dev Calculates the amount of LUTE that can be boosted based on the deposited amount.
* @param depositedLUTEAmount_ The amount of LUTE deposited.
* @return The amount of LUTE that will be boosted.
*/
function calculateBoostLUTEAmount(uint256 depositedLUTEAmount_) public view override returns (uint256) {
return depositedLUTEAmount_ >= _getMinLUTEAmountForBoost() ? (depositedLUTEAmount_ * _boostLUTEPercentage) / _PRECISION : 0;
}
/**
* @dev Calculates the minimum amount of LUTE tokens required for receiving a boost, based on the USD threshold.
* Utilizes the current LUTE to USD price from the specified price provider to convert the minimum USD amount
* into its equivalent LUTE amount. This ensures that the boost mechanism adapts to changes in the LUTE token's value,
* maintaining the intended economic threshold for participation.
* @return The calculated minimum amount of LUTE tokens required for a boost, based on the current LUTE to USD price.
*/
function _getMinLUTEAmountForBoost() internal view returns (uint256) {
return (IPriceProvider(priceProvider).getUsdToLUTEPrice() * minUSDAmount) / _LUTE_PREICSION;
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/mocks/BribeUpgradeableMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "../bribes/BribeUpgradeable.sol";
contract BribeUpgradeableMockWithFixTargetEpoch is BribeUpgradeable {
uint256 immutable __mock_targetEpoch;
constructor(uint256 mock_targetEpoch) {
__mock_targetEpoch = mock_targetEpoch;
}
// it is copy of earnedWithTimestamp function but public
function earnedWithTimestampPublic(address _owner, address _rewardToken) public view returns (uint256, uint256) {
uint256 k = 0;
uint256 reward = 0;
uint256 _endTimestamp = IMinter(minter).active_period(); // claim until current epoch
uint256 _userLastTime = userTimestamp[_owner][_rewardToken];
// if user first time then set it to first bribe - week to avoid any timestamp problem
if (_userLastTime < firstBribeTimestamp) {
_userLastTime = firstBribeTimestamp - WEEK;
}
for (k; k < 50; k++) {
if (_userLastTime == _endTimestamp) {
// if we reach the current epoch, exit
break;
}
reward += _earned(_owner, _rewardToken, _userLastTime);
_userLastTime += WEEK;
}
return (reward, _userLastTime);
}
function fixVotingPowerForPreviusEpoch(
uint256 tokenId_,
uint256 newBalance_
) external onlyAllowed whenRewardClaimPaused reinitializer(2) {
uint256 targetEpoch = (block.timestamp / WEEK) * WEEK - WEEK;
require(targetEpoch == __mock_targetEpoch, "invalid epoch to fix");
address tokenOwner = IVotingEscrow(ve).ownerOf(tokenId_);
uint256 balance = _balances[tokenOwner][targetEpoch];
_totalSupply[targetEpoch] -= balance;
_totalSupply[targetEpoch] += newBalance_;
_balances[tokenOwner][targetEpoch] = newBalance_;
if (balance > 0) {
emit Withdrawn(tokenId_, balance);
}
emit Staked(tokenId_, newBalance_);
}
}
@openzeppelin/contracts/proxy/Clones.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol)
pragma solidity ^0.8.0;
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*
* _Available since v3.4._
*/
library Clones {
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(0, 0x09, 0x37)
}
require(instance != address(0), "ERC1167: create failed");
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(0, 0x09, 0x37, salt)
}
require(instance != address(0), "ERC1167: create2 failed");
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := keccak256(add(ptr, 0x43), 0x55)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
}
@openzeppelin/contracts-upgradeable/utils/math/SignedMathUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMathUpgradeable {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
contracts/dexV2/interfaces/IPairFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IPairFactory {
event PairCreated(address indexed token0, address indexed token1, bool stable, address pair, uint);
event SetPaused(bool state);
event SetCommunityVaultFactory(address indexed communityVaultFactory);
event SetIsPublicPoolCreationMode(bool mode);
event SetProtocolFee(uint256 fee);
event SetCustomProtocolFee(address indexed pair, uint256 fee);
event SetCustomFee(address indexed pair, uint256 fee);
event SetFee(bool stable, uint256 fee);
event SetCustomVolatileDynamicFeeModule(address indexed pair, address indexed module);
error IncorrcectFee();
error IncorrectPair();
error IdenticalAddress();
error PairExist();
function implementation() external view returns (address);
function PAIRS_ADMINISTRATOR_ROLE() external view returns (bytes32);
function FEES_MANAGER_ROLE() external view returns (bytes32);
function PAIRS_CREATOR_ROLE() external view returns (bytes32);
function hasRole(bytes32 role, address user) external view returns (bool);
function allPairsLength() external view returns (uint);
function isPair(address pair) external view returns (bool);
function allPairs(uint index) external view returns (address);
function getPair(address tokenA, address token, bool stable) external view returns (address);
function createPair(address tokenA, address tokenB, bool stable) external returns (address pair);
function pairs() external view returns (address[] memory);
function getFee(address pair_, bool stable_) external view returns (uint256);
function getHookTarget(address pair_) external view returns (address);
function getProtocolFee(address pair_) external view returns (uint256);
function isPaused() external view returns (bool);
function isPublicPoolCreationMode() external view returns (bool);
function getCustomVolatileDynamicFeeModule(address pair_) external view returns (address);
}
@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates merkle trees that are safe
* against this attack out of the box.
*/
library MerkleProofUpgradeable {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
require(proofPos == proofLen, "MerkleProof: invalid multiproof");
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
require(proofPos == proofLen, "MerkleProof: invalid multiproof");
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
@openzeppelin/contracts/proxy/utils/Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}
contracts/mocks/ERC20Mock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC20Mock is ERC20 {
uint8 internal _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
_decimals = decimals_;
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
function mint(address to_, uint256 amount_) external {
_mint(to_, amount_);
}
}
@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.0;
import "../Proxy.sol";
import "./ERC1967Upgrade.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy, ERC1967Upgrade {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
* function call, and allows initializing the storage of the proxy like a Solidity constructor.
*/
constructor(address _logic, bytes memory _data) payable {
_upgradeToAndCall(_logic, _data, false);
}
/**
* @dev Returns the current implementation address.
*/
function _implementation() internal view virtual override returns (address impl) {
return ERC1967Upgrade._getImplementation();
}
}
@openzeppelin/contracts/access/AccessControlEnumerable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControlEnumerable.sol";
import "./AccessControl.sol";
import "../utils/structs/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_roleMembers[role].add(account);
}
/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
super._revokeRole(role, account);
_roleMembers[role].remove(account);
}
}
@openzeppelin/contracts/access/IAccessControlEnumerable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
contracts/bribes/rewards/BribeVeLUTERewardToken.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {IERC20Upgradeable, SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {IVotingEscrow} from "../../core/interfaces/IVotingEscrow.sol";
import {IBribeVeLUTERewardToken} from "./interfaces/IBribeVeLUTERewardToken.sol";
/**
* @title BribeVeLUTERewardToken
* @notice This contract serves as an intermediary token (brVeLUTE) used to facilitate
* the conversion of LUTE rewards into veLUTE NFTs via a VotingEscrow contract. Users can
* receive these intermediary tokens (brVeLUTE) when they deposit LUTE. When these tokens
* are transferred to non-whitelisted addresses, they are automatically burned and
* converted into veLUTE NFTs by locking LUTE in the VotingEscrow contract. This flow is
* particularly useful for "bribe" mechanisms, where veLUTE positions are desired like bribes
* without directly managing lock creation and extension.
*/
contract BribeVeLUTERewardToken is IBribeVeLUTERewardToken, ERC20Upgradeable, AccessControlUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @notice Role identifier for entities allowed to mint brVeLUTE tokens.
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
/// @notice Role identifier for addresses exempt from automatic conversion when receiving brVeLUTE.
bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE");
/// @notice Address of the VotingEscrow contract which mints veLUTE NFTs.
address public votingEscrow;
/// @notice Address of the underlying LUTE token that gets locked in the VotingEscrow.
address public underlyingToken;
/// @notice Parameters used when calling createLockFor() in the VotingEscrow contract.
CreateLockParams public override createLockParams;
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract.
* @dev This function must be called only once. Sets up roles, token name/symbol, and references to VotingEscrow.
* @param votingEscrow_ The address of the VotingEscrow contract.
*/
function initialize(address votingEscrow_) external initializer {
__ERC20_init("Bribe veLUTE Reward Token", "brVeLUTE");
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
votingEscrow = votingEscrow_;
underlyingToken = IVotingEscrow(votingEscrow).token();
createLockParams = CreateLockParams({
lockDuration: 15724800,
shouldBoosted: false,
withPermanentLock: false,
managedTokenIdForAttach: 0
});
}
/**
* @notice Updates the parameters used for creating new veLUTE locks.
* @dev Only callable by an address holding the DEFAULT_ADMIN_ROLE.
* This function sets new values that dictate how veLUTE locks are created when
* intermediary tokens are transferred to non-whitelisted addresses.
* @param createLockParams_ The new parameters specifying lock duration, boosting,
* permanent lock setting, and associated managed token ID.
*
* Emits an {UpdateCreateLockParams} event.
*/
function updateCreateLockParams(CreateLockParams memory createLockParams_) external onlyRole(DEFAULT_ADMIN_ROLE) {
createLockParams = createLockParams_;
emit UpdateCreateLockParams(createLockParams_);
}
/**
* @notice Mints brVeLUTE tokens in exchange for underlying LUTE tokens.
* @dev The caller must have the MINTER_ROLE. The caller transfers LUTE to this contract,
* which can later be locked into veLUTE when transferred out to non-whitelisted addresses.
* @param to_ The address to receive the minted brVeLUTE tokens.
* @param amount_ The amount of LUTE provided and thus the amount of brVeLUTE minted.
*/
function mint(address to_, uint256 amount_) external override onlyRole(MINTER_ROLE) {
IERC20Upgradeable(underlyingToken).safeTransferFrom(_msgSender(), address(this), amount_);
_mint(to_, amount_);
}
/**
* @dev Hook that is called after any token transfer, including minting and burning.
* If tokens are transferred to a non-whitelisted address (and the sender is not a minter),
* the transferred amount of brVeLUTE is immediately burned and converted into a veLUTE position
* via the VotingEscrow contract.
*
* Requirements:
* - The conversion only occurs if:
* - `from_` is not zero and does not have MINTER_ROLE, and
* - `to_` is not zero and does not have WHITELIST_ROLE.
*
* @param from_ The address sending the tokens.
* @param to_ The address receiving the tokens.
* @param amount_ The number of tokens transferred.
*/
function _afterTokenTransfer(address from_, address to_, uint256 amount_) internal virtual override {
if (to_ == address(0) || from_ == address(0)) {
return;
}
if (hasRole(MINTER_ROLE, from_) || hasRole(WHITELIST_ROLE, to_)) {
return;
}
_burn(to_, amount_);
IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
IERC20Upgradeable(underlyingToken).forceApprove(address(votingEscrowCache), amount_);
CreateLockParams memory createLockParamsCache = createLockParams;
votingEscrowCache.createLockFor(
amount_,
createLockParamsCache.lockDuration,
to_,
createLockParamsCache.shouldBoosted,
createLockParamsCache.withPermanentLock,
createLockParamsCache.managedTokenIdForAttach
);
}
}
contracts/dexV2/interfaces/ICustomVolatileDynamicFee.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
interface ICustomVolatileDynamicFee {
function isEnable() external view returns (bool);
function getFee(address pair_) external view returns (bool, uint256);
}
@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
contracts/core/interfaces/ILute.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
/**
* @title Interface of the Lute main ERC20 token
* @author Lute Protocol team
*/
interface ILute is IERC20 {
/**
* @dev Allows the contract owner to mint new tokens to a specified address.
* @param to_ The address to receive the minted tokens.
* @param amount_ The number of tokens to mint.
*/
function mint(address to_, uint256 amount_) external;
}
contracts/core/libraries/LibVotingEscrowConstants.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
uint256 constant WEEK = 604800;
uint256 constant MAX_LOCK_TIME = 15724800;
int128 constant I128_MAX_LOCK_TIME = 15724800;
contracts/integration/interfaces/IDistributionCreator.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
struct DistributionParameters {
// ID of the reward (populated once created). This can be left as a null bytes32 when creating distributions
// on Merkl.
bytes32 rewardId;
// Address of the UniswapV3 pool that needs to be incentivized
address uniV3Pool;
// Address of the reward token for the incentives
address rewardToken;
// Amount of `rewardToken` to distribute across all the epochs
// Amount distributed per epoch is `amount/numEpoch`
uint256 amount;
// List of all position wrappers to consider or not for this contract. Some wrappers like Gamma or Arrakis
// are automatically detected and so there is no need to specify them here. Check out the docs to find out
// which need to be specified and which are not automatically detected.
address[] positionWrappers;
// Type (blacklist==3, whitelist==0, ...) encoded as a `uint32` for each wrapper in the list above. Mapping between
// wrapper types and their corresponding `uint32` value can be found in Angle Docs
uint32[] wrapperTypes;
// In the incentivization formula, how much of the fees should go to holders of token0
// in base 10**4
uint32 propToken0;
// Proportion for holding token1 (in base 10**4)
uint32 propToken1;
// Proportion for providing a useful liquidity (in base 10**4) that generates fees
uint32 propFees;
// Timestamp at which the incentivization should start. This is in the same units as `block.timestamp`.
uint32 epochStart;
// Amount of epochs for which incentivization should last. Epochs are expressed in hours here, so for a
// campaign of 1 week `numEpoch` should for instance be 168.
uint32 numEpoch;
// Whether out of range liquidity should still be incentivized or not
// This should be equal to 1 if out of range liquidity should still be incentivized
// and 0 otherwise.
uint32 isOutOfRangeIncentivized;
// How much more addresses with a maximum boost can get with respect to addresses
// which do not have a boost (in base 4). In the case of Curve where addresses get 2.5x more
// this would be 25000.
uint32 boostedReward;
// Address of the token which dictates who gets boosted rewards or not. This is optional
// and if the zero address is given no boost will be taken into account. In the case of Curve, this address
// would for instance be the veBoostProxy address, or in other cases the veToken address.
address boostingAddress;
// Additional data passed when distributing rewards. This parameter may be used in case
// the reward distribution script needs to look into other parameters beyond the ones above.
// In most cases, when creating a campaign on Merkl, you can leave this as an empty bytes.
bytes additionalData;
}
interface IDistributionCreator {
/// @notice Creates a `distribution` to incentivize a given pool for a specific period of time
/// @return distributionAmount How many reward tokens are actually taken into consideration in the contract
/// @dev If the address specified as a UniV3 pool is not effectively a pool, it will not be handled by the
/// distribution script and rewards may be lost
/// @dev Reward tokens sent as part of distributions must have been whitelisted before and amounts
/// sent should be bigger than a minimum amount specific to each token
/// @dev The `positionWrappers` specified in the `distribution` struct need to be supported by the script
/// List of supported `positionWrappers` can be found in the docs.
/// @dev If the pool incentivized contains one whitelisted token, then no fees are taken on the rewards
/// @dev This function reverts if the sender has not signed the message `messageHash` once through one of
/// the functions enabling to sign
function createDistribution(DistributionParameters memory distribution) external returns (uint256 distributionAmount);
function isWhitelistedToken(address token) external view returns (uint256);
function rewardTokenMinAmounts(address token) external view returns (uint256);
function acceptConditions() external;
}
@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
contracts/integration/interfaces/IOpenOceanCaller.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
interface IOpenOceanCaller {
struct CallDescription {
uint256 target;
uint256 gasLimit;
uint256 value;
bytes data;
}
function makeCall(CallDescription memory desc) external;
function makeCalls(CallDescription[] memory desc) external payable;
}
@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolState.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Pool state that can change
/// @dev Important security note: when using this data by external contracts, it is necessary to take into account the possibility
/// of manipulation (including read-only reentrancy).
/// This interface is based on the UniswapV3 interface, credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolState {
/// @notice Safely get most important state values of Algebra Integral AMM
/// @dev Several values exposed as a single method to save gas when accessed externally.
/// **Important security note: this method checks reentrancy lock and should be preferred in most cases**.
/// @return sqrtPrice The current price of the pool as a sqrt(dToken1/dToken0) Q64.96 value
/// @return tick The current global tick of the pool. May not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick boundary
/// @return lastFee The current (last known) pool fee value in hundredths of a bip, i.e. 1e-6 (so '100' is '0.01%'). May be obsolete if using dynamic fee plugin
/// @return pluginConfig The current plugin config as bitmap. Each bit is responsible for enabling/disabling the hooks, the last bit turns on/off dynamic fees logic
/// @return activeLiquidity The currently in-range liquidity available to the pool
/// @return nextTick The next initialized tick after current global tick
/// @return previousTick The previous initialized tick before (or at) current global tick
function safelyGetStateOfAMM()
external
view
returns (uint160 sqrtPrice, int24 tick, uint16 lastFee, uint8 pluginConfig, uint128 activeLiquidity, int24 nextTick, int24 previousTick);
/// @notice Allows to easily get current reentrancy lock status
/// @dev can be used to prevent read-only reentrancy.
/// This method just returns `globalState.unlocked` value
/// @return unlocked Reentrancy lock flag, true if the pool currently is unlocked, otherwise - false
function isUnlocked() external view returns (bool unlocked);
// ! IMPORTANT security note: the pool state can be manipulated.
// ! The following methods do not check reentrancy lock themselves.
/// @notice The globalState structure in the pool stores many values but requires only one slot
/// and is exposed as a single method to save gas when accessed externally.
/// @dev **important security note: caller should check `unlocked` flag to prevent read-only reentrancy**
/// @return price The current price of the pool as a sqrt(dToken1/dToken0) Q64.96 value
/// @return tick The current tick of the pool, i.e. according to the last tick transition that was run
/// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick boundary
/// @return lastFee The current (last known) pool fee value in hundredths of a bip, i.e. 1e-6 (so '100' is '0.01%'). May be obsolete if using dynamic fee plugin
/// @return pluginConfig The current plugin config as bitmap. Each bit is responsible for enabling/disabling the hooks, the last bit turns on/off dynamic fees logic
/// @return communityFee The community fee represented as a percent of all collected fee in thousandths, i.e. 1e-3 (so 100 is 10%)
/// @return unlocked Reentrancy lock flag, true if the pool currently is unlocked, otherwise - false
function globalState() external view returns (uint160 price, int24 tick, uint16 lastFee, uint8 pluginConfig, uint16 communityFee, bool unlocked);
/// @notice Look up information about a specific tick in the pool
/// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @param tick The tick to look up
/// @return liquidityTotal The total amount of position liquidity that uses the pool either as tick lower or tick upper
/// @return liquidityDelta How much liquidity changes when the pool price crosses the tick
/// @return prevTick The previous tick in tick list
/// @return nextTick The next tick in tick list
/// @return outerFeeGrowth0Token The fee growth on the other side of the tick from the current tick in token0
/// @return outerFeeGrowth1Token The fee growth on the other side of the tick from the current tick in token1
/// In addition, these values are only relative and must be used only in comparison to previous snapshots for
/// a specific position.
function ticks(
int24 tick
)
external
view
returns (
uint256 liquidityTotal,
int128 liquidityDelta,
int24 prevTick,
int24 nextTick,
uint256 outerFeeGrowth0Token,
uint256 outerFeeGrowth1Token
);
/// @notice The timestamp of the last sending of tokens to community vault
/// @return The timestamp truncated to 32 bits
function communityFeeLastTimestamp() external view returns (uint32);
/// @notice The amounts of token0 and token1 that will be sent to the vault
/// @dev Will be sent COMMUNITY_FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp
/// @return communityFeePending0 The amount of token0 that will be sent to the vault
/// @return communityFeePending1 The amount of token1 that will be sent to the vault
function getCommunityFeePending() external view returns (uint128 communityFeePending0, uint128 communityFeePending1);
/// @notice Returns the address of currently used plugin
/// @dev The plugin is subject to change
/// @return pluginAddress The address of currently used plugin
function plugin() external view returns (address pluginAddress);
/// @notice The contract to which community fees are transferred
/// @return communityVaultAddress The communityVault address
function communityVault() external view returns (address communityVaultAddress);
/// @notice Returns 256 packed tick initialized boolean values. See TickTree for more information
/// @param wordPosition Index of 256-bits word with ticks
/// @return The 256-bits word with packed ticks info
function tickTable(int16 wordPosition) external view returns (uint256);
/// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint256
/// @return The fee growth accumulator for token0
function totalFeeGrowth0Token() external view returns (uint256);
/// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
/// @dev This value can overflow the uint256
/// @return The fee growth accumulator for token1
function totalFeeGrowth1Token() external view returns (uint256);
/// @notice The current pool fee value
/// @dev In case dynamic fee is enabled in the pool, this method will call the plugin to get the current fee.
/// If the plugin implements complex fee logic, this method may return an incorrect value or revert.
/// In this case, see the plugin implementation and related documentation.
/// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @return currentFee The current pool fee value in hundredths of a bip, i.e. 1e-6
function fee() external view returns (uint16 currentFee);
/// @notice The tracked token0 and token1 reserves of pool
/// @dev If at any time the real balance is larger, the excess will be transferred to liquidity providers as additional fee.
/// If the balance exceeds uint128, the excess will be sent to the communityVault.
/// @return reserve0 The last known reserve of token0
/// @return reserve1 The last known reserve of token1
function getReserves() external view returns (uint128 reserve0, uint128 reserve1);
/// @notice Returns the information about a position by the position's key
/// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @param key The position's key is a packed concatenation of the owner address, bottomTick and topTick indexes
/// @return liquidity The amount of liquidity in the position
/// @return innerFeeGrowth0Token Fee growth of token0 inside the tick range as of the last mint/burn/poke
/// @return innerFeeGrowth1Token Fee growth of token1 inside the tick range as of the last mint/burn/poke
/// @return fees0 The computed amount of token0 owed to the position as of the last mint/burn/poke
/// @return fees1 The computed amount of token1 owed to the position as of the last mint/burn/poke
function positions(
bytes32 key
) external view returns (uint256 liquidity, uint256 innerFeeGrowth0Token, uint256 innerFeeGrowth1Token, uint128 fees0, uint128 fees1);
/// @notice The currently in range liquidity available to the pool
/// @dev This value has no relationship to the total liquidity across all ticks.
/// Returned value cannot exceed type(uint128).max
/// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @return The current in range liquidity
function liquidity() external view returns (uint128);
/// @notice The current tick spacing
/// @dev Ticks can only be initialized by new mints at multiples of this value
/// e.g.: a tickSpacing of 60 means ticks can be initialized every 60th tick, i.e., ..., -120, -60, 0, 60, 120, ...
/// However, tickspacing can be changed after the ticks have been initialized.
/// This value is an int24 to avoid casting even though it is always positive.
/// @return The current tick spacing
function tickSpacing() external view returns (int24);
/// @notice The previous initialized tick before (or at) current global tick
/// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @return The previous initialized tick
function prevTickGlobal() external view returns (int24);
/// @notice The next initialized tick after current global tick
/// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @return The next initialized tick
function nextTickGlobal() external view returns (int24);
/// @notice The root of tick search tree
/// @dev Each bit corresponds to one node in the second layer of tick tree: '1' if node has at least one active bit.
/// **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @return The root of tick search tree as bitmap
function tickTreeRoot() external view returns (uint32);
/// @notice The second layer of tick search tree
/// @dev Each bit in node corresponds to one node in the leafs layer (`tickTable`) of tick tree: '1' if leaf has at least one active bit.
/// **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
/// @return The node of tick search tree second layer
function tickTreeSecondLayer(int16) external view returns (uint256);
}
contracts/gauges/GaugeUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IGaugeFactory} from "./interfaces/IGaugeFactory.sol";
import {IRewarder} from "./interfaces/IRewarder.sol";
import {IMerklGaugeMiddleman} from "../integration/interfaces/IMerklGaugeMiddleman.sol";
import {IPairIntegrationInfo} from "../integration/interfaces/IPairIntegrationInfo.sol";
import {IPairInfo} from "../dexV2/interfaces/IPairInfo.sol";
import {IPair} from "../dexV2/interfaces/IPair.sol";
import {IBribe} from "../bribes/interfaces/IBribe.sol";
import {IGauge} from "./interfaces/IGauge.sol";
import {IFeesVault} from "../fees/interfaces/IFeesVault.sol";
import {UpgradeCall} from "../integration/UpgradeCall.sol";
contract GaugeUpgradeable is IGauge, ReentrancyGuardUpgradeable, UpgradeCall {
using SafeERC20 for IERC20;
enum GaugeType {
None,
V2PairsGauge,
V3PairsGauge
}
GaugeType public immutable gaugeType;
bool public isDistributeEmissionToMerkle;
bool public emergency;
IERC20 public rewardToken;
address public TOKEN;
address public VE;
address public DISTRIBUTION;
address public gaugeRewarder;
address public internal_bribe;
address public external_bribe;
address public feeVault;
address public gaugeFactory;
address public merklGaugeMiddleman;
uint256 public DURATION;
uint256 internal _periodFinish;
uint256 public rewardRate;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
uint256 internal _totalSupply;
mapping(address => uint256) internal _balances;
event RewardAdded(uint256 reward);
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event Harvest(address indexed user, uint256 reward);
event ClaimFees(address indexed from, uint256 claimed0, uint256 claimed1);
event EmergencyActivated(address indexed gauge, uint256 timestamp);
event EmergencyDeactivated(address indexed gauge, uint256 timestamp);
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
modifier onlyDistribution() {
require(msg.sender == DISTRIBUTION, "Caller is not RewardsDistribution contract");
_;
}
modifier isNotEmergency() {
require(emergency == false);
_;
}
constructor(GaugeType gaugeType_) {
_disableInitializers();
gaugeType = gaugeType_;
}
function initialize(
address _rewardToken,
address _ve,
address _token,
address _distribution,
address _internal_bribe,
address _external_bribe,
bool _isDistributeEmissionToMerkle,
address _merklGaugeMiddleman,
address _feeVault
) external initializer {
__ReentrancyGuard_init();
gaugeFactory = msg.sender;
rewardToken = IERC20(_rewardToken); // main reward
VE = _ve; // vested
TOKEN = _token; // underlying (LP)
DISTRIBUTION = _distribution; // distro address (voter)
DURATION = 7 * 86400; // distro time
internal_bribe = _internal_bribe; // lp fees goes here
external_bribe = _external_bribe; // bribe fees goes here
isDistributeEmissionToMerkle = _isDistributeEmissionToMerkle;
if (_isDistributeEmissionToMerkle) {
require(_merklGaugeMiddleman != address(0), "not setup merklGaugeMiddleman");
}
merklGaugeMiddleman = _merklGaugeMiddleman;
feeVault = _feeVault;
emergency = false; // emergency flag
}
/* -----------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
ONLY OWNER
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------------------------------------------- */
modifier onlyOwner() {
require(msg.sender == IGaugeFactory(gaugeFactory).gaugeOwner());
_;
}
///@notice set distribution address (should be GaugeProxyL2)
function setDistribution(address _distribution) external onlyOwner {
require(_distribution != address(0), "zero addr");
require(_distribution != DISTRIBUTION, "same addr");
DISTRIBUTION = _distribution;
}
///@notice set distribution address (should be GaugeProxyL2)
function setMerklGaugeMiddleman(address _newMerklGaugeMiddleman) external onlyOwner {
require(_newMerklGaugeMiddleman != address(0));
merklGaugeMiddleman = _newMerklGaugeMiddleman;
}
///@notice set distribution address (should be GaugeProxyL2)
function setIsDistributeEmissionToMerkle(bool _isDistributeEmissionToMerkle) external onlyOwner {
if (_isDistributeEmissionToMerkle) {
require(merklGaugeMiddleman != address(0));
}
isDistributeEmissionToMerkle = _isDistributeEmissionToMerkle;
}
///@notice set gauge rewarder address
function setGaugeRewarder(address _gaugeRewarder) external onlyOwner {
require(_gaugeRewarder != gaugeRewarder, "same addr");
gaugeRewarder = _gaugeRewarder;
}
///@notice set feeVault address
function setFeeVault(address _feeVault) external onlyOwner {
require(_feeVault != address(0), "zero addr");
require(_feeVault != feeVault, "same addr");
feeVault = _feeVault;
}
///@notice set new internal bribe contract (where to send fees)
function setInternalBribe(address _int) external onlyOwner {
require(_int != address(0), "zero");
internal_bribe = _int;
}
function activateEmergencyMode() external onlyOwner {
require(emergency == false, "emergency");
emergency = true;
emit EmergencyActivated(address(this), block.timestamp);
}
function stopEmergencyMode() external onlyOwner {
require(emergency == true, "emergency");
emergency = false;
emit EmergencyDeactivated(address(this), block.timestamp);
}
/* -----------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
VIEW FUNCTIONS
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------------------------------------------- */
///@notice total supply held
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
///@notice balance of a user
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
///@notice last time reward
function lastTimeRewardApplicable() public view returns (uint256) {
return Math.min(block.timestamp, _periodFinish);
}
///@notice reward for a single token
function rewardPerToken() public view returns (uint256) {
if (_totalSupply == 0) {
return rewardPerTokenStored;
} else {
return rewardPerTokenStored + ((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18) / _totalSupply;
}
}
///@notice see earned rewards for user
function earned(address account) public view returns (uint256) {
return rewards[account] + (_balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18;
}
///@notice get total reward for the duration
function rewardForDuration() external view returns (uint256) {
return rewardRate * DURATION;
}
function periodFinish() external view returns (uint256) {
return _periodFinish;
}
/* -----------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
USER INTERACTION
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------------------------------------------- */
///@notice deposit all TOKEN of msg.sender
function depositAll() external {
_deposit(IERC20(TOKEN).balanceOf(msg.sender), msg.sender);
}
///@notice deposit amount TOKEN
function deposit(uint256 amount) external {
_deposit(amount, msg.sender);
}
///@notice deposit internal
function _deposit(uint256 amount, address account) internal nonReentrant isNotEmergency updateReward(account) {
require(amount > 0, "deposit(Gauge): cannot stake 0");
_balances[account] = _balances[account] + (amount);
_totalSupply = _totalSupply + (amount);
IERC20(TOKEN).safeTransferFrom(account, address(this), amount);
if (address(gaugeRewarder) != address(0)) {
IRewarder(gaugeRewarder).onReward(account, account, _balances[account]);
}
emit Deposit(account, amount);
}
///@notice withdraw all token
function withdrawAll() external {
_withdraw(_balances[msg.sender]);
}
///@notice withdraw a certain amount of TOKEN
function withdraw(uint256 amount) external {
_withdraw(amount);
}
///@notice withdraw internal
function _withdraw(uint256 amount) internal nonReentrant isNotEmergency updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
require(_balances[msg.sender] > 0, "no balances");
_totalSupply = _totalSupply - (amount);
_balances[msg.sender] = _balances[msg.sender] - (amount);
if (address(gaugeRewarder) != address(0)) {
IRewarder(gaugeRewarder).onReward(msg.sender, msg.sender, _balances[msg.sender]);
}
IERC20(TOKEN).safeTransfer(msg.sender, amount);
emit Withdraw(msg.sender, amount);
}
function emergencyWithdraw() external nonReentrant {
require(emergency, "emergency");
require(_balances[msg.sender] > 0, "no balances");
uint256 _amount = _balances[msg.sender];
_totalSupply = _totalSupply - (_amount);
_balances[msg.sender] = 0;
IERC20(TOKEN).safeTransfer(msg.sender, _amount);
emit Withdraw(msg.sender, _amount);
}
function emergencyWithdrawAmount(uint256 _amount) external nonReentrant {
require(emergency, "emergency");
require(_balances[msg.sender] >= _amount, "no balances");
_totalSupply = _totalSupply - (_amount);
_balances[msg.sender] -= _amount;
IERC20(TOKEN).safeTransfer(msg.sender, _amount);
emit Withdraw(msg.sender, _amount);
}
///@notice withdraw all TOKEN and harvest rewardToken
function withdrawAllAndHarvest() external {
_withdraw(_balances[msg.sender]);
getReward();
}
///@notice User harvest function called from distribution (voter allows harvest on multiple gauges)
function getReward(address _user) public nonReentrant onlyDistribution updateReward(_user) {
uint256 reward = rewards[_user];
if (reward > 0) {
rewards[_user] = 0;
rewardToken.safeTransfer(_user, reward);
emit Harvest(_user, reward);
}
if (gaugeRewarder != address(0)) {
IRewarder(gaugeRewarder).onReward(_user, _user, _balances[_user]);
}
}
///@notice User harvest function
function getReward() public nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardToken.safeTransfer(msg.sender, reward);
emit Harvest(msg.sender, reward);
}
if (gaugeRewarder != address(0)) {
IRewarder(gaugeRewarder).onReward(msg.sender, msg.sender, _balances[msg.sender]);
}
}
/* -----------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
DISTRIBUTION
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------------------------------------------- */
/// @dev Receive rewards from distribution
function notifyRewardAmount(
address token,
uint256 reward
) external nonReentrant isNotEmergency onlyDistribution updateReward(address(0)) {
require(token == address(rewardToken), "not rew token");
rewardToken.safeTransferFrom(DISTRIBUTION, address(this), reward);
if (isDistributeEmissionToMerkle) {
rewardToken.safeTransfer(merklGaugeMiddleman, reward);
IMerklGaugeMiddleman(merklGaugeMiddleman).notifyReward(address(this), 0);
} else {
if (block.timestamp >= _periodFinish) {
rewardRate = reward / (DURATION);
} else {
uint256 remaining = _periodFinish - (block.timestamp);
uint256 leftover = remaining * (rewardRate);
rewardRate = (reward + leftover) / DURATION;
}
// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint256 balance = rewardToken.balanceOf(address(this));
require(rewardRate <= balance / (DURATION), "Provided reward too high");
}
lastUpdateTime = block.timestamp;
_periodFinish = block.timestamp + (DURATION);
emit RewardAdded(reward);
}
function claimFees() external nonReentrant returns (uint256 claimed0, uint256 claimed1) {
return _claimFees();
}
function _claimFees() internal returns (uint256 claimed0, uint256 claimed1) {
address _token = address(TOKEN);
(claimed0, claimed1) = IFeesVault(feeVault).claimFees();
if (gaugeType == GaugeType.V2PairsGauge) {
(uint256 poolClaimedToken0, uint256 poolClaimedToken1) = IPair(_token).claimFees();
claimed0 += poolClaimedToken0;
claimed1 += poolClaimedToken1;
}
if (claimed0 > 0 || claimed1 > 0) {
uint256 _fees0 = claimed0;
uint256 _fees1 = claimed1;
address _token0 = IPairIntegrationInfo(_token).token0();
address _token1 = IPairIntegrationInfo(_token).token1();
if (_fees0 > 0) {
IERC20(_token0).forceApprove(internal_bribe, 0);
IERC20(_token0).forceApprove(internal_bribe, _fees0);
IBribe(internal_bribe).notifyRewardAmount(_token0, _fees0);
}
if (_fees1 > 0) {
IERC20(_token1).forceApprove(internal_bribe, 0);
IERC20(_token1).forceApprove(internal_bribe, _fees1);
IBribe(internal_bribe).notifyRewardAmount(_token1, _fees1);
}
emit ClaimFees(msg.sender, claimed0, claimed1);
}
}
}
contracts/lute/libraries/VirtualRewarderCheckpoints.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
/**
* @title VirtualRewarderCheckpoints
* @dev Library to manage checkpoints in a virtual reward system. This library facilitates the storage of state
* at specific timestamps for historical data tracking and reward calculation.
*/
library VirtualRewarderCheckpoints {
struct Checkpoint {
uint256 timestamp; // Timestamp at which the checkpoint is logged
uint256 amount; // Amount or value associated with the checkpoint
}
/**
* @notice Writes a new checkpoint or updates an existing one in the mapping.
* @dev If a checkpoint at the given timestamp already exists, it updates the amount; otherwise, it creates a new checkpoint.
*
* @param self_ Mapping from index to Checkpoint.
* @param lastIndex_ Index of the last recorded checkpoint.
* @param timestamp_ Timestamp for the new checkpoint.
* @param amount_ Amount to be associated with the new checkpoint.
* @return newIndex The index of the newly written checkpoint.
*
* Example:
* mapping(uint256 => Checkpoint) checkpoints;
* uint256 lastIndex = 0;
* lastIndex = VirtualRewarderCheckpoints.writeCheckpoint(checkpoints, lastIndex, block.timestamp, 100);
*/
function writeCheckpoint(
mapping(uint256 index => Checkpoint checkpoint) storage self_,
uint256 lastIndex_,
uint256 timestamp_,
uint256 amount_
) internal returns (uint256 newIndex) {
Checkpoint memory last = self_[lastIndex_];
newIndex = last.timestamp == timestamp_ ? lastIndex_ : lastIndex_ + 1;
self_[newIndex] = Checkpoint({timestamp: timestamp_, amount: amount_});
}
/**
* @notice Retrieves the amount at the checkpoint closest to and not after the given timestamp.
*
* @param self_ Mapping from index to Checkpoint.
* @param lastIndex_ Index of the last checkpoint.
* @param timestamp_ Timestamp for querying the amount.
* @return amount The amount at the closest checkpoint.
*
* Example:
* uint256 amount = VirtualRewarderCheckpoints.getAmount(checkpoints, lastIndex, block.timestamp);
*/
function getAmount(
mapping(uint256 index => Checkpoint checkpoint) storage self_,
uint256 lastIndex_,
uint256 timestamp_
) internal view returns (uint256) {
return self_[getCheckpointIndex(self_, lastIndex_, timestamp_)].amount;
}
/**
* @notice Retrieves the index of the checkpoint that is nearest to and less than or equal to the given timestamp.
* @dev Performs a binary search to find the closest timestamp, which is efficient on sorted data.
*
* @param self_ Mapping from index to Checkpoint.
* @param lastIndex_ Index of the last checkpoint.
* @param timestamp_ Timestamp to query the nearest checkpoint for.
* @return index The index of the closest checkpoint by timestamp.
*
* Example:
* uint256 index = VirtualRewarderCheckpoints.getCheckpointIndex(checkpoints, lastIndex, block.timestamp - 10);
*/
function getCheckpointIndex(
mapping(uint256 index => Checkpoint checkpoint) storage self_,
uint256 lastIndex_,
uint256 timestamp_
) internal view returns (uint256) {
if (lastIndex_ == 0) {
return 0;
}
if (self_[lastIndex_].timestamp <= timestamp_) {
return lastIndex_;
}
if (self_[0].timestamp > timestamp_) {
return 0;
}
uint256 start;
uint256 end = lastIndex_;
while (end > start) {
uint256 middle = end - (end - start) / 2;
Checkpoint memory checkpoint = self_[middle];
if (checkpoint.timestamp == timestamp_) {
return middle;
} else if (checkpoint.timestamp < timestamp_) {
start = middle;
} else {
end = middle - 1;
}
}
return start;
}
}
contracts/utils/UtilsUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {IUpgradeCall} from "../integration/interfaces/IUgradeCall.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract UtilsUpgradeable is Initializable {
function multiUpgradeCall(address[] calldata targets_) external virtual {
for (uint256 i; i < targets_.length; ) {
IUpgradeCall(targets_[i]).upgradeCall();
unchecked {
i++;
}
}
}
}
@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/extensions/ERC721Pausable.sol)
pragma solidity ^0.8.0;
import "../ERC721.sol";
import "../../../security/Pausable.sol";
/**
* @dev ERC721 token with pausable token transfers, minting and burning.
*
* Useful for scenarios such as preventing trades until the end of an evaluation
* period, or having an emergency switch for freezing all token transfers in the
* event of a large bug.
*
* IMPORTANT: This contract does not include public pause and unpause functions. In
* addition to inheriting this contract, you must define both functions, invoking the
* {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate
* access control, e.g. using {AccessControl} or {Ownable}. Not doing so will
* make the contract unpausable.
*/
abstract contract ERC721Pausable is ERC721, Pausable {
/**
* @dev See {ERC721-_beforeTokenTransfer}.
*
* Requirements:
*
* - the contract must not be paused.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
require(!paused(), "ERC721Pausable: token transfer while paused");
}
}
@openzeppelin/contracts-upgradeable/access/IAccessControlEnumerableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControlUpgradeable.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerableUpgradeable is IAccessControlUpgradeable {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
pragma solidity ^0.8.2;
import "../beacon/IBeacon.sol";
import "../../interfaces/IERC1967.sol";
import "../../interfaces/draft-IERC1822.sol";
import "../../utils/Address.sol";
import "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*
* _Available since v4.1._
*/
abstract contract ERC1967Upgrade is IERC1967 {
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation address.
*/
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Perform implementation upgrade
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Perform implementation upgrade with additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
_upgradeTo(newImplementation);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(newImplementation, data);
}
}
/**
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
// Upgrades from old implementations will perform a rollback test. This test requires the new
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
// this special case will break upgrade paths from old UUPS implementation to new ones.
if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
_setImplementation(newImplementation);
} else {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
} catch {
revert("ERC1967Upgrade: new implementation is not UUPS");
}
_upgradeToAndCall(newImplementation, data, forceCall);
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*/
function _getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _changeAdmin(address newAdmin) internal {
emit AdminChanged(_getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
*/
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function _getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
require(
Address.isContract(IBeacon(newBeacon).implementation()),
"ERC1967: beacon implementation is not a contract"
);
StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
}
/**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
}
}
}
contracts/lute/interfaces/ICompoundVeLUTEManagedNFTStrategy.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IManagedNFTStrategy} from "./IManagedNFTStrategy.sol";
import {ISingelTokenBuyback} from "./ISingelTokenBuyback.sol";
/**
* @title ICompoundVeLUTEManagedNFTStrategy
* @dev Interface for a compound strategy specific to VeLUTE tokens, extending the basic managed NFT strategy functionality.
* @notice This interface provides functionalities to handle compounding of VeLUTE token rewards and interactions with a virtual rewarder contract.
*/
interface ICompoundVeLUTEManagedNFTStrategy is IManagedNFTStrategy, ISingelTokenBuyback {
/**
* @dev Emitted when rewards are compounded by the caller.
*
* @param caller The address of the account that called the compound function.
* @param amount The amount of VeLUTE tokens that were compounded.
*/
event Compound(address indexed caller, uint256 indexed amount);
/**
* @dev Emitted when an NFT is attached to the strategy, initializing reward mechanisms for it.
*
* @param tokenId The ID of the NFT that is being attached.
* @param userBalance The balance associated with the NFT at the time of attachment.
*/
event OnAttach(uint256 indexed tokenId, uint256 indexed userBalance);
/**
* @dev Emitted when an NFT is detached from the strategy, concluding reward mechanisms for it.
*
* @param tokenId The ID of the NFT that is being detached.
* @param userBalance The balance associated with the NFT at the time of detachment.
* @param lockedRewards The rewards that were locked and harvested upon detachment.
*/
event OnDettach(uint256 indexed tokenId, uint256 indexed userBalance, uint256 indexed lockedRewards);
/**
* @dev Emitted when ERC20 tokens are recovered from the contract by an admin.
*
* @param caller The address of the caller who initiated the recovery.
* @param recipient The recipient address where the recovered tokens were sent.
* @param token The address of the token that was recovered.
* @param amount The amount of the token that was recovered.
*/
event Erc20Recover(address indexed caller, address indexed recipient, address indexed token, uint256 amount);
/**
* @dev Emitted when ERC721 tokens are recovered from the contract by an admin.
*
* @param caller The address of the caller who initiated the recovery.
* @param recipient The recipient address where the recovered tokens were sent.
* @param token The address of the token that was recovered.
* @param tokenIds The array of token identifiers that are wtihdrawed
*/
event Erc721Recover(address indexed caller, address indexed recipient, address indexed token, uint256[] tokenIds);
/**
* @dev Emitted when the address of the Router V2 Path Provider is updated.
*
* @param oldRouterV2PathProvider The address of the previous Router V2 Path Provider.
* @param newRouterV2PathProvider The address of the new Router V2 Path Provider that has been set.
*/
event SetRouterV2PathProvider(address indexed oldRouterV2PathProvider, address indexed newRouterV2PathProvider);
/**
* @notice Emitted when the per-strategy detachment lock duration is updated.
* @param previousDuration Previous duration in seconds (0 means "use manager default").
* @param newDuration New duration in seconds (0 means "use manager default").
*/
event SetDetachmentLockDuration(uint256 previousDuration, uint256 newDuration);
/**
* @notice Compounds accumulated rewards into additional stakes or holdings.
* @dev Function to reinvest earned rewards back into the underlying asset to increase the principal amount.
* This is specific to strategies dealing with compounding mechanisms in DeFi protocols.
*/
function compound() external;
/**
* @notice Merges (compounds) all veNFTs owned by this strategy except the managed one (`managedTokenId`).
* @dev Checks if there is more than one veNFT owned. If only the managed one is present,
* it reverts with `NotOtherVeNFTsAvailable()`.
*/
function compoundVeNFTsAll() external;
/**
* @notice Merges (compounds) the specified list of veNFT IDs into the managed veNFT.
* @dev Ensures that each veNFT ID is actually owned by this contract
* @param tokenIds_ The list of veNFT IDs to be merged.
*/
function compoundVeNFTs(uint256[] calldata tokenIds_) external;
/**
* @notice Returns the address of the virtual rewarder associated with this strategy.
* @return address The contract address of the virtual rewarder that manages reward distributions for this strategy.
*/
function virtualRewarder() external view returns (address);
/**
* @notice Returns the address of the lute token used in this strategy.
* @return address The contract address of the lute token.
*/
function lute() external view returns (address);
/**
* @notice Retrieves the total amount of locked rewards available for a specific NFT based on its tokenId.
* @param tokenId_ The identifier of the NFT to query.
* @return The total amount of locked rewards for the specified NFT.
*/
function getLockedRewardsBalance(uint256 tokenId_) external view returns (uint256);
/**
* @notice Retrieves the balance or stake associated with a specific NFT.
* @param tokenId_ The identifier of the NFT to query.
* @return The balance of the specified NFT.
*/
function balanceOf(uint256 tokenId_) external view returns (uint256);
/**
* @notice Retrieves the total supply of stakes managed by the strategy.
* @return The total supply of stakes.
*/
function totalSupply() external view returns (uint256);
/**
* @notice Claims bribes for the current strategy and recovers specified ERC20 tokens to a recipient.
* @dev This function allows the strategy to claim bribes from specified contracts and transfer
* non-strategic ERC20 tokens back to the designated recipient in a single transaction.
* @param bribes_ The list of addresses representing bribe contracts from which to claim rewards.
* @param tokens_ A nested array where each entry corresponds to a list of token addresses to claim from the respective bribe contract.
* @param recipient_ The address to which recovered tokens should be sent.
* @param tokensToRecover_ The list of ERC20 token addresses to be recovered and transferred to the recipient.
*
* Emits:
* - Emits `Erc20Recover` for each recovered token.
*/
function claimBribesWithERC20Recover(
address[] calldata bribes_,
address[][] calldata tokens_,
address recipient_,
address[] calldata tokensToRecover_
) external;
/**
* @notice Claims bribes from multiple addresses and recovers both specified ERC20 tokens and specified veNFTs to the given recipient.
* @dev Extends `claimBribesWithERC20Recover` by also recovering veNFTs if `veNftTokenIdsToRecover_` is non-empty.
* Protected by `_checkBuybackSwapPermissions()`.
* @param bribes_ Array of addresses from which to claim bribes.
* @param tokens_ Nested array of token addresses corresponding to each bribe address.
* @param recipient_ The address to which recovered tokens/NFTs are sent.
* @param tokensToRecover_ The list of ERC20 tokens to be recovered and transferred to `recipient_`.
* @param veNftTokenIdsToRecover_ The list of veNFT IDs to be recovered and transferred to `recipient_`.
*/
function claimBribesWithTokensRecover(
address[] calldata bribes_,
address[][] calldata tokens_,
address recipient_,
address[] calldata tokensToRecover_,
uint256[] calldata veNftTokenIdsToRecover_
) external;
/**
* @notice Recovers specified NFT tokens from this contract to a given recipient.
* @param recipient_ The address receiving the recovered NFTs.
* @param token_ The NFT contract address (e.g. `votingEscrow` or other ERC721).
* @param tokenIds_ The list of NFT IDs to transfer.
*/
function erc721Recover(address recipient_, address token_, uint256[] calldata tokenIds_) external;
/**
* @notice Initializes the contract with necessary operational addresses, and sets specific strategy parameters.
*
* @param managedNFTManager_ Address of the managed NFT manager contract.
* @param virtualRewarder_ Address of the virtual rewarder contract.
* @param name_ Name of the strategy.
*/
function initialize(
address managedNFTManager_,
address virtualRewarder_,
address routerV2PathProvider_,
string memory name_
) external;
}
contracts/gauges/PerpetualsTradersRewarderUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;
import {EIP712Upgradeable, ECDSAUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {IERC20Upgradeable, SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IPerpetualsTradersRewarder} from "./interfaces/IPerpetualsTradersRewarder.sol";
/**
* @title PerpetualsTradersRewarderUpgradeable
* @dev Implementation of the IPerpetualsTradersRewarder interface. Manages reward distribution to perpetual traders.
*/
contract PerpetualsTradersRewarderUpgradeable is IPerpetualsTradersRewarder, OwnableUpgradeable, EIP712Upgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @notice The address of the gauge
address public override gauge;
/// @notice The address of the reward token
address public override token;
/// @notice The address of the signer
address public override signer;
/// @notice The total amount of tokens reward
uint256 public override totalReward;
/// @notice The total amount of tokens claimed
uint256 public override totalClaimed;
/// @notice Mapping of user addresses to the amount of tokens claimed
mapping(address => uint256) public claimed;
bytes32 internal constant _MESSAGE_TYPEHASH = keccak256("Message(address user,uint256 amount)");
// Errors
/// @dev Error thrown when the provided signature is invalid
error InvalidSignature();
/// @dev Error thrown when claim functionality is disabled
error ClaimDisabled();
/// @dev Error thrown when a user tries to claim an already claimed amount
error AlreadyClaimed();
/// @dev Error thrown when an unauthorized address attempts to access restricted functionality
error AccessDenied();
/// @dev Error thrown when the provided reward token address is incorrect
error IncorrectRewardToken();
error AddressZero();
/**
* @dev Initializes the contract by disabling initializers.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract.
* @param gauge_ The address of the gauge.
* @param token_ The address of the reward token.
* @param signer_ The address of the signer.
*/
function initialize(address gauge_, address token_, address signer_) external initializer {
_checkAddressZero(token_);
_checkAddressZero(gauge_);
__EIP712_init("PerpetualsTradersRewarderUpgradeable", "1");
__Ownable_init();
gauge = gauge_;
token = token_;
signer = signer_;
}
/**
* @notice Sets the signer address.
* @param signer_ The address of the new signer.
*/
function setSigner(address signer_) external onlyOwner {
signer = signer_;
emit SetSigner(signer_);
}
/**
* @notice Notifies a reward amount.
* @param token_ The address of the reward token.
* @param rewardAmount_ The amount of reward tokens.
*/
function notifyRewardAmount(address token_, uint256 rewardAmount_) external {
if (_msgSender() != gauge) {
revert AccessDenied();
}
if (token_ != token) {
revert IncorrectRewardToken();
}
IERC20Upgradeable(token).safeTransferFrom(_msgSender(), address(this), rewardAmount_);
totalReward += rewardAmount_;
emit Reward(_msgSender(), block.timestamp, rewardAmount_);
}
/**
* @notice Claims the reward for the user.
* @param amount_ The amount of tokens to claim.
* @param signature_ The signature of the claim.
* @return reward The amount of reward tokens claimed.
*/
function claim(uint256 amount_, bytes memory signature_) external returns (uint256 reward) {
if (signer == address(0)) {
revert ClaimDisabled();
}
uint256 claimedAmount = claimed[_msgSender()];
if (amount_ <= claimedAmount) {
revert AlreadyClaimed();
}
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(_MESSAGE_TYPEHASH, _msgSender(), amount_)));
if (ECDSAUpgradeable.recover(digest, signature_) != signer) {
revert InvalidSignature();
}
reward = amount_ - claimedAmount;
claimed[_msgSender()] = amount_;
totalClaimed += reward;
IERC20Upgradeable(token).safeTransfer(_msgSender(), reward);
emit Claim(_msgSender(), block.timestamp, reward);
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@cryptoalgebra/integral-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title The interface for the Algebra volatility oracle
/// @dev This contract stores timepoints and calculates statistical averages
interface IVolatilityOracle {
/// @notice Returns data belonging to a certain timepoint
/// @param index The index of timepoint in the array
/// @dev There is more convenient function to fetch a timepoint: getTimepoints(). Which requires not an index but seconds
/// @return initialized Whether the timepoint has been initialized and the values are safe to use
/// @return blockTimestamp The timestamp of the timepoint
/// @return tickCumulative The tick multiplied by seconds elapsed for the life of the pool as of the timepoint timestamp
/// @return volatilityCumulative Cumulative standard deviation for the life of the pool as of the timepoint timestamp
/// @return tick The tick at blockTimestamp
/// @return averageTick Time-weighted average tick
/// @return windowStartIndex Index of closest timepoint >= WINDOW seconds ago
function timepoints(
uint256 index
)
external
view
returns (
bool initialized,
uint32 blockTimestamp,
int56 tickCumulative,
uint88 volatilityCumulative,
int24 tick,
int24 averageTick,
uint16 windowStartIndex
);
/// @notice Returns the index of the last timepoint that was written.
/// @return index of the last timepoint written
function timepointIndex() external view returns (uint16);
/// @notice Returns the timestamp of the last timepoint that was written.
/// @return timestamp of the last timepoint
function lastTimepointTimestamp() external view returns (uint32);
/// @notice Returns information about whether oracle is initialized
/// @return true if oracle is initialized, otherwise false
function isInitialized() external view returns (bool);
/// @dev Reverts if a timepoint at or before the desired timepoint timestamp does not exist.
/// 0 may be passed as `secondsAgo' to return the current cumulative values.
/// If called with a timestamp falling between two timepoints, returns the counterfactual accumulator values
/// at exactly the timestamp between the two timepoints.
/// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors
/// @param secondsAgo The amount of time to look back, in seconds, at which point to return a timepoint
/// @return tickCumulative The cumulative tick since the pool was first initialized, as of `secondsAgo`
/// @return volatilityCumulative The cumulative volatility value since the pool was first initialized, as of `secondsAgo`
function getSingleTimepoint(uint32 secondsAgo) external view returns (int56 tickCumulative, uint88 volatilityCumulative);
/// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos`
/// @dev Reverts if `secondsAgos` > oldest timepoint
/// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors
/// @param secondsAgos Each amount of time to look back, in seconds, at which point to return a timepoint
/// @return tickCumulatives The cumulative tick since the pool was first initialized, as of each `secondsAgo`
/// @return volatilityCumulatives The cumulative volatility values since the pool was first initialized, as of each `secondsAgo`
function getTimepoints(uint32[] memory secondsAgos) external view returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives);
/// @notice Fills uninitialized timepoints with nonzero value
/// @dev Can be used to reduce the gas cost of future swaps
/// @param startIndex The start index, must be not initialized
/// @param amount of slots to fill, startIndex + amount must be <= type(uint16).max
function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external;
}
contracts/core/GaugeRewarder.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {AccessControlEnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import "./interfaces/IVoter.sol";
import "./interfaces/IMinter.sol";
import "./interfaces/IGaugeRewarder.sol";
/**
* @title GaugeRewarder
* @dev This contract is responsible for managing reward distributions to gauges and handling claims.
* It allows setting rewards, transferring rewards, and claiming rewards based on a signature.
*/
contract GaugeRewarder is IGaugeRewarder, AccessControlEnumerableUpgradeable, EIP712Upgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @dev Role for claiming rewards on behalf of users.
*/
bytes32 internal constant _CLAMER_FOR_ROLE = keccak256("CLAMER_FOR_ROLE");
/**
* @dev Role for managing reward notifications.
*/
bytes32 internal constant _REWARDER_ROLE = keccak256("REWARDER_ROLE");
/**
* @dev Type hash for EIP-712 claim signature.
*/
bytes32 internal constant _CLAIM_TYPEHASH = keccak256("Claim(address user,uint256 totalAmount,uint256 deadline)");
/**
* @notice The address of the reward token.
*/
address public token;
/**
* @notice The address of the minter contract.
*/
address public minter;
/**
* @notice The address of the voter contract.
*/
address public voter;
/**
* @notice The address of the authorized signer for reward claims.
*/
address public signer;
/**
* @notice The total amount of rewards distributed so far.
*/
uint256 public totalRewardDistributed;
/**
* @notice The total amount of rewards claimed so far.
*/
uint256 public totalRewardClaimed;
/**
* @notice A mapping to track the claimed reward amounts for each address.
*/
mapping(address => uint256) public claimed;
/**
* @notice A mapping to track rewards per gauge per epoch.
* @dev Maps the epoch to gauge addresses and their corresponding rewards.
*/
mapping(uint256 epoch => mapping(address gauge => uint256)) public rewardPerGaugePerEpoch;
/**
* @notice A mapping to track rewards per epoch.
* @dev Maps the epoch to the total reward for that epoch.
*/
mapping(uint256 epoch => uint256) public rewardPerEpoch;
/**
* @dev Error thrown when attempting to distribute zero reward amount.
*/
error ZeroRewardAmount();
/**
* @dev Error thrown when access is denied for the requested action.
*/
error AccessDenied();
/**
* @dev Error thrown when a signature has expired.
*/
error SignatureExpired();
/**
* @dev Error thrown when claiming is disabled.
*/
error ClaimDisabled();
/**
* @dev Error thrown when an address has already claimed the reward.
*/
error AlreadyClaimed();
/**
* @dev Error thrown when an invalid signature is provided for a claim.
*/
error InvalidSignature();
/**
* @dev Error thrown when there is insufficient available balance for the reward.
*/
error InsufficientAvailableBalance();
error AddressZero();
/**
* @dev Modifier to restrict access to either a gauge or an authorized rewarder.
*/
modifier onlyGaugeOrRewarder() {
if (!IVoter(voter).isGauge(_msgSender()) && !hasRole(_REWARDER_ROLE, _msgSender())) {
revert AccessDenied();
}
_;
}
/**
* @dev Initializes the contract by disabling initializers.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract with the specified parameters.
* @param token_ The address of the reward token.
* @param voter_ The address of the voter contract.
* @param minter_ The address of the minter contract.
*/
function initialize(address token_, address voter_, address minter_) external initializer {
_checkAddressZero(token_);
_checkAddressZero(minter_);
_checkAddressZero(voter_);
__EIP712_init("GaugeRewarder", "1");
__AccessControlEnumerable_init();
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
token = token_;
minter = minter_;
voter = voter_;
}
/**
* @notice Sets the signer address for reward claims.
* @param signer_ The address of the new signer.
*/
function setSigner(address signer_) external onlyRole(DEFAULT_ADMIN_ROLE) {
signer = signer_;
emit SetSigner(signer_);
}
/**
* @notice Notifies a reward for a specified gauge.
* @param gauge_ The address of the gauge to receive the reward.
* @param amount_ The amount of reward tokens.
*/
function notifyReward(address gauge_, uint256 amount_) external virtual onlyGaugeOrRewarder {
_notifyReward(gauge_, amount_);
}
/**
* @notice Transfers tokens and notifies a reward for a specified gauge.
* @param gauge_ The address of the gauge to receive the reward.
* @param amount_ The amount of reward tokens.
*/
function notifyRewardWithTransfer(address gauge_, uint256 amount_) external virtual onlyGaugeOrRewarder {
IERC20Upgradeable(token).safeTransferFrom(_msgSender(), address(this), amount_);
_notifyReward(gauge_, amount_);
}
/**
* @notice Claims rewards on behalf for specified target address.
* @param target_ The address of the recipient of the claimed reward.
* @param totalAmount_ The total amount of reward being claimed.
* @param deadline_ The expiration time of the claim.
* @param signature_ The signature authorizing the claim.
* @return The amount of reward claimed.
*/
function claimFor(
address target_,
uint256 totalAmount_,
uint256 deadline_,
bytes memory signature_
) external onlyRole(_CLAMER_FOR_ROLE) returns (uint256) {
return _claim(target_, totalAmount_, deadline_, signature_);
}
/**
* @notice Claims rewards for the caller.
* @param totalAmount_ The total amount of reward being claimed.
* @param deadline_ The expiration time of the claim.
* @param signature_ The signature authorizing the claim.
* @return The amount of reward claimed.
*/
function claim(uint256 totalAmount_, uint256 deadline_, bytes memory signature_) external returns (uint256) {
return _claim(_msgSender(), totalAmount_, deadline_, signature_);
}
/**
* @dev Internal function to notify rewards for a gauge.
* @param gauge_ The address of the gauge to receive the reward.
* @param amount_ The amount of reward tokens.
*/
function _notifyReward(address gauge_, uint256 amount_) internal {
uint256 availableBalance = IERC20Upgradeable(token).balanceOf(address(this)) - (totalRewardDistributed - totalRewardClaimed);
if (amount_ == 0) {
amount_ = availableBalance;
} else if (amount_ > availableBalance) {
revert InsufficientAvailableBalance();
}
if (amount_ == 0) {
revert ZeroRewardAmount();
}
uint256 epoch = IMinter(minter).active_period();
totalRewardDistributed += amount_;
rewardPerEpoch[epoch] += amount_;
rewardPerGaugePerEpoch[epoch][gauge_] += amount_;
emit NotifyReward(_msgSender(), gauge_, IMinter(minter).active_period(), amount_);
}
/**
* @dev Internal function to handle reward claims.
* @param target_ The address of the recipient of the claimed reward.
* @param totalAmount_ The total amount of reward being claimed.
* @param deadline_ The expiration time of the claim.
* @param signature_ The signature authorizing the claim.
* @return reward The amount of reward claimed.
*/
function _claim(
address target_,
uint256 totalAmount_,
uint256 deadline_,
bytes memory signature_
) internal virtual returns (uint256 reward) {
if (signer == address(0)) {
revert ClaimDisabled();
}
if (deadline_ <= block.timestamp) {
revert SignatureExpired();
}
uint256 claimedAmount = claimed[target_];
if (totalAmount_ <= claimedAmount) {
revert AlreadyClaimed();
}
if (
ECDSAUpgradeable.recover(
_hashTypedDataV4(keccak256(abi.encode(_CLAIM_TYPEHASH, target_, totalAmount_, deadline_))),
signature_
) != signer
) {
revert InvalidSignature();
}
reward = totalAmount_ - claimedAmount;
claimed[target_] = totalAmount_;
totalRewardClaimed += reward;
IERC20Upgradeable(token).safeTransfer(target_, reward);
emit Claim(target_, reward, totalAmount_);
}
/**
* @dev Checks if an address is zero and reverts if it is.
* @param addr_ The address to check.
* @notice Reverts with `AddressZero` if the address is zero.
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert AddressZero();
}
}
}
@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0 = a * b; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
assembly {
result := div(prod0, denominator)
}
return result;
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
// Subtract 256 bit remainder from 512 bit number
assembly {
let remainder := mulmod(a, b, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (0 - denominator) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the preconditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
if (a == 0 || ((result = a * b) / a == b)) {
require(denominator > 0);
assembly {
result := add(div(result, denominator), gt(mod(result, denominator), 0))
}
} else {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}
}
/// @notice Returns ceil(x / y)
/// @dev division by 0 has unspecified behavior, and must be checked externally
/// @param x The dividend
/// @param y The divisor
/// @return z The quotient, ceil(x / y)
function unsafeDivRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := add(div(x, y), gt(mod(x, y), 0))
}
}
}
@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolEvents.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Events emitted by a pool
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolEvents {
/// @notice Emitted exactly once by a pool when #initialize is first called on the pool
/// @dev Mint/Burn/Swaps cannot be emitted by the pool before Initialize
/// @param price The initial sqrt price of the pool, as a Q64.96
/// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
event Initialize(uint160 price, int24 tick);
/// @notice Emitted when liquidity is minted for a given position
/// @param sender The address that minted the liquidity
/// @param owner The owner of the position and recipient of any minted liquidity
/// @param bottomTick The lower tick of the position
/// @param topTick The upper tick of the position
/// @param liquidityAmount The amount of liquidity minted to the position range
/// @param amount0 How much token0 was required for the minted liquidity
/// @param amount1 How much token1 was required for the minted liquidity
event Mint(
address sender,
address indexed owner,
int24 indexed bottomTick,
int24 indexed topTick,
uint128 liquidityAmount,
uint256 amount0,
uint256 amount1
);
/// @notice Emitted when fees are collected by the owner of a position
/// @param owner The owner of the position for which fees are collected
/// @param recipient The address that received fees
/// @param bottomTick The lower tick of the position
/// @param topTick The upper tick of the position
/// @param amount0 The amount of token0 fees collected
/// @param amount1 The amount of token1 fees collected
event Collect(address indexed owner, address recipient, int24 indexed bottomTick, int24 indexed topTick, uint128 amount0, uint128 amount1);
/// @notice Emitted when a position's liquidity is removed
/// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
/// @param owner The owner of the position for which liquidity is removed
/// @param bottomTick The lower tick of the position
/// @param topTick The upper tick of the position
/// @param liquidityAmount The amount of liquidity to remove
/// @param amount0 The amount of token0 withdrawn
/// @param amount1 The amount of token1 withdrawn
event Burn(address indexed owner, int24 indexed bottomTick, int24 indexed topTick, uint128 liquidityAmount, uint256 amount0, uint256 amount1);
/// @notice Emitted by the pool for any swaps between token0 and token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the output of the swap
/// @param amount0 The delta of the token0 balance of the pool
/// @param amount1 The delta of the token1 balance of the pool
/// @param price The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of price of the pool after the swap
event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick);
/// @notice Emitted by the pool for any flashes of token0/token1
/// @param sender The address that initiated the swap call, and that received the callback
/// @param recipient The address that received the tokens from flash
/// @param amount0 The amount of token0 that was flashed
/// @param amount1 The amount of token1 that was flashed
/// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
/// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
event Flash(address indexed sender, address indexed recipient, uint256 amount0, uint256 amount1, uint256 paid0, uint256 paid1);
/// @notice Emitted when the community fee is changed by the pool
/// @param communityFeeNew The updated value of the community fee in thousandths (1e-3)
event CommunityFee(uint16 communityFeeNew);
/// @notice Emitted when the tick spacing changes
/// @param newTickSpacing The updated value of the new tick spacing
event TickSpacing(int24 newTickSpacing);
/// @notice Emitted when the plugin address changes
/// @param newPluginAddress New plugin address
event Plugin(address newPluginAddress);
/// @notice Emitted when the plugin config changes
/// @param newPluginConfig New plugin config
event PluginConfig(uint8 newPluginConfig);
/// @notice Emitted when the fee changes inside the pool
/// @param fee The current fee in hundredths of a bip, i.e. 1e-6
event Fee(uint16 fee);
event CommunityVault(address newCommunityVault);
}
contracts/core/VeLuteDistributorUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {AccessControlEnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
import {IVeLuteDistributor} from "./interfaces/IVeLuteDistributor.sol";
/**
* @title VeLuteDistributorUpgradeable
* @notice A contract to distribute veLute tokens to specified recipients by locking LUTE tokens in the Voting Escrow contract.
* @dev
* - Inherits from:
* 1) IVeLuteDistributor (interface with function signatures for distribution and recovery).
* 2) AccessControlEnumerableUpgradeable (for role-based access control).
* - The contract allows authorized roles to set whitelisted "reasons" for airdrops, distribute locked LUTE (veLute),
* and recover tokens if needed.
*/
contract VeLuteDistributorUpgradeable is IVeLuteDistributor, AccessControlEnumerableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @notice Role identifier for accounts allowed to initiate veLute distributions.
*/
bytes32 internal constant _DISTRIBUTOR_ROLE = keccak256("DISTRIBUTOR_ROLE");
/**
* @notice Role identifier for accounts allowed to recover tokens (withdraw).
*/
bytes32 internal constant _WITHDRAWER_ROLE = keccak256("WITHDRAWER_ROLE");
/**
* @notice The address of the LUTE token contract (to be locked for veLute).
*/
address public lute;
/**
* @notice The address of the Voting Escrow contract used to create veLute.
*/
address public votingEscrow;
/**
* @dev Maps the keccak256 hash of a reason string to a boolean indicating if it is whitelisted.
*/
mapping(bytes32 => bool) internal _isWhitelistedReasons;
/// @notice Thrown when the contract's balance of LUTE is insufficient for distribution.
error InsufficientBalance();
/// @notice Thrown if the recipient address is the zero address.
error ZeroRecipientAddress();
/// @notice Thrown if the provided reason is not whitelisted.
error NotWhitelistedReason();
/// @notice Thrown if the provided arrayies with diff length.
error ArrayLengthMismatch();
error AddressZero();
/**
* @notice Constructor that ensures the implementation contract cannot be initialized more than once.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract, setting up the LUTE and Voting Escrow addresses and granting admin roles.
* @param lute_ The address of the LUTE token contract (non-zero).
* @param votingEscrow_ The address of the Voting Escrow contract (non-zero).
* @dev Grants the DEFAULT_ADMIN_ROLE to the deployer (caller of `initialize`).
*/
function initialize(address lute_, address votingEscrow_) external initializer {
_checkAddressZero(lute_);
_checkAddressZero(votingEscrow_);
__AccessControlEnumerable_init();
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
lute = lute_;
votingEscrow = votingEscrow_;
}
/**
* @notice Updates the whitelisting status of multiple airdrop reasons.
* @param reasons_ An array of reasons to set or unset from the whitelist.
* @param isWhitelisted_ A matching array of booleans indicating whether each reason is whitelisted.
* @dev Emitted via {SetWhitelistReasons}.
*/
function setWhitelistReasons(string[] calldata reasons_, bool[] calldata isWhitelisted_) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (reasons_.length != isWhitelisted_.length) {
revert ArrayLengthMismatch();
}
for (uint256 i; i < reasons_.length; ) {
_isWhitelistedReasons[keccak256(abi.encode(reasons_[i]))] = isWhitelisted_[i];
unchecked {
i++;
}
}
emit SetWhitelistReasons(reasons_, isWhitelisted_);
}
/**
* @notice Checks if a given reason is whitelisted.
* @param reason_ The reason string to check.
* @return A boolean indicating if the reason is whitelisted.
*/
function isWhitelistedReason(string memory reason_) public view returns (bool) {
return _isWhitelistedReasons[keccak256(abi.encode(reason_))];
}
/**
* @notice Distributes veLute tokens to specified recipients by locking LUTE tokens in the Voting Escrow contract.
* @dev
* - Requires the caller to have the `_DISTRIBUTOR_ROLE`.
* - Verifies that `reason_` is whitelisted. If not, reverts with {NotWhitelistedReason}.
* - Calculates the total sum of LUTE tokens needed. If the contract does not have enough, reverts with {InsufficientBalance}.
* - Locks LUTE in the Voting Escrow for each recipient, creating veLute positions.
* - Emits {AirdropVeLuteTotal} after distributing to all recipients in this batch.
* - Emits {AirdropVeLute} for each individual recipient.
* @param reason_ A whitelisted string describing the airdrop reason.
* @param rows_ An array of AirdropRow structs that specify each recipient, lock duration, amount, etc.
*/
function distributeVeLute(string memory reason_, AidropRow[] calldata rows_) external override onlyRole(_DISTRIBUTOR_ROLE) {
if (!isWhitelistedReason(reason_)) {
revert NotWhitelistedReason();
}
IERC20Upgradeable luteCache = IERC20Upgradeable(lute);
IVotingEscrow veCache = IVotingEscrow(votingEscrow);
uint256 totalDistributionSum;
for (uint256 i; i < rows_.length; ) {
if (rows_[i].recipient == address(0)) {
revert ZeroRecipientAddress();
}
totalDistributionSum += rows_[i].amount;
unchecked {
i++;
}
}
if (totalDistributionSum > luteCache.balanceOf(address(this))) revert InsufficientBalance();
luteCache.forceApprove(address(veCache), totalDistributionSum);
for (uint256 i; i < rows_.length; ) {
AidropRow memory row = rows_[i];
uint256 tokenId = veCache.createLockFor(
row.amount,
row.lockDuration,
row.recipient,
false,
row.withPermanentLock,
row.managedTokenIdForAttach
);
emit AirdropVeLute(row.recipient, reason_, tokenId, row.amount);
unchecked {
i++;
}
}
emit AidropVeLuteTotal(_msgSender(), reason_, totalDistributionSum);
}
/**
* @notice Allows the holder of `_WITHDRAWER_ROLE` to recover tokens from this contract.
* @dev
* - This can be used to retrieve any ERC20 token that was mistakenly sent to this contract.
* @param token_ The address of the token to recover.
* @param recoverAmount_ The amount of tokens to recover.
* @custom:emits RecoverToken
*/
function recoverTokens(address token_, uint256 recoverAmount_) external override onlyRole(_WITHDRAWER_ROLE) {
IERC20Upgradeable(token_).safeTransfer(msg.sender, recoverAmount_);
emit RecoverToken(token_, recoverAmount_);
}
/**
* @notice Checks if the provided address is zero and reverts if it is.
* @param addr_ The address to check.
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
}
@cryptoalgebra/integral-core/contracts/interfaces/pool/IAlgebraPoolActions.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Permissionless pool actions
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolActions {
/// @notice Sets the initial price for the pool
/// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
/// @dev Initialization should be done in one transaction with pool creation to avoid front-running
/// @param initialPrice The initial sqrt price of the pool as a Q64.96
function initialize(uint160 initialPrice) external;
/// @notice Adds liquidity for the given recipient/bottomTick/topTick position
/// @dev The caller of this method receives a callback in the form of IAlgebraMintCallback#algebraMintCallback
/// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
/// on bottomTick, topTick, the amount of liquidity, and the current price.
/// @param leftoversRecipient The address which will receive potential surplus of paid tokens
/// @param recipient The address for which the liquidity will be created
/// @param bottomTick The lower tick of the position in which to add liquidity
/// @param topTick The upper tick of the position in which to add liquidity
/// @param liquidityDesired The desired amount of liquidity to mint
/// @param data Any data that should be passed through to the callback
/// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
/// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
/// @return liquidityActual The actual minted amount of liquidity
function mint(
address leftoversRecipient,
address recipient,
int24 bottomTick,
int24 topTick,
uint128 liquidityDesired,
bytes calldata data
) external returns (uint256 amount0, uint256 amount1, uint128 liquidityActual);
/// @notice Collects tokens owed to a position
/// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
/// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
/// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
/// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
/// @param recipient The address which should receive the fees collected
/// @param bottomTick The lower tick of the position for which to collect fees
/// @param topTick The upper tick of the position for which to collect fees
/// @param amount0Requested How much token0 should be withdrawn from the fees owed
/// @param amount1Requested How much token1 should be withdrawn from the fees owed
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(
address recipient,
int24 bottomTick,
int24 topTick,
uint128 amount0Requested,
uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);
/// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
/// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
/// @dev Fees must be collected separately via a call to #collect
/// @param bottomTick The lower tick of the position for which to burn liquidity
/// @param topTick The upper tick of the position for which to burn liquidity
/// @param amount How much liquidity to burn
/// @param data Any data that should be passed through to the plugin
/// @return amount0 The amount of token0 sent to the recipient
/// @return amount1 The amount of token1 sent to the recipient
function burn(int24 bottomTick, int24 topTick, uint128 amount, bytes calldata data) external returns (uint256 amount0, uint256 amount1);
/// @notice Swap token0 for token1, or token1 for token0
/// @dev The caller of this method receives a callback in the form of IAlgebraSwapCallback#algebraSwapCallback
/// @param recipient The address to receive the output of the swap
/// @param zeroToOne The direction of the swap, true for token0 to token1, false for token1 to token0
/// @param amountRequired The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
/// @param limitSqrtPrice The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
/// @param data Any data to be passed through to the callback. If using the Router it should contain SwapRouter#SwapCallbackData
/// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
/// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
function swap(
address recipient,
bool zeroToOne,
int256 amountRequired,
uint160 limitSqrtPrice,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
/// @notice Swap token0 for token1, or token1 for token0 with prepayment
/// @dev The caller of this method receives a callback in the form of IAlgebraSwapCallback#algebraSwapCallback
/// caller must send tokens in callback before swap calculation
/// the actually sent amount of tokens is used for further calculations
/// @param leftoversRecipient The address which will receive potential surplus of paid tokens
/// @param recipient The address to receive the output of the swap
/// @param zeroToOne The direction of the swap, true for token0 to token1, false for token1 to token0
/// @param amountToSell The amount of the swap, only positive (exact input) amount allowed
/// @param limitSqrtPrice The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
/// value after the swap. If one for zero, the price cannot be greater than this value after the swap
/// @param data Any data to be passed through to the callback. If using the Router it should contain SwapRouter#SwapCallbackData
/// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
/// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
function swapWithPaymentInAdvance(
address leftoversRecipient,
address recipient,
bool zeroToOne,
int256 amountToSell,
uint160 limitSqrtPrice,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
/// @dev The caller of this method receives a callback in the form of IAlgebraFlashCallback#algebraFlashCallback
/// @dev All excess tokens paid in the callback are distributed to currently in-range liquidity providers as an additional fee.
/// If there are no in-range liquidity providers, the fee will be transferred to the first active provider in the future
/// @param recipient The address which will receive the token0 and token1 amounts
/// @param amount0 The amount of token0 to send
/// @param amount1 The amount of token1 to send
/// @param data Any data to be passed through to the callback
function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
}
contracts/gauges/interfaces/IGauge.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IGauge {
function TOKEN() external view returns (address);
function notifyRewardAmount(address token, uint amount) external;
function getReward(address account) external;
function earned(address account) external view returns (uint256);
function periodFinish() external view returns (uint256);
function rewardRate() external view returns (uint256);
function claimFees() external returns (uint claimed0, uint claimed1);
function balanceOf(address _account) external view returns (uint);
function totalSupply() external view returns (uint);
function setDistribution(address _distro) external;
function activateEmergencyMode() external;
function stopEmergencyMode() external;
function setInternalBribe(address intbribe) external;
function setGaugeRewarder(address _gr) external;
function setFeeVault(address _feeVault) external;
function initialize(
address _rewardToken,
address _ve,
address _token,
address _distribution,
address _internal_bribe,
address _external_bribe,
bool _isToMerkleDistributor,
address _merklGaugeMiddleman,
address _feeVault
) external;
}
@openzeppelin/contracts-upgradeable/utils/Base64Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides a set of functions to operate with Base64 strings.
*
* _Available since v4.5._
*/
library Base64Upgradeable {
/**
* @dev Base64 Encoding/Decoding Table
*/
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* @dev Converts a `bytes` to its Bytes64 `string` representation.
*/
function encode(bytes memory data) internal pure returns (string memory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
if (data.length == 0) return "";
// Loads the table into memory
string memory table = _TABLE;
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
// and split into 4 numbers of 6 bits.
// The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
// - `data.length + 2` -> Round up
// - `/ 3` -> Number of 3-bytes chunks
// - `4 *` -> 4 characters for each chunk
string memory result = new string(4 * ((data.length + 2) / 3));
/// @solidity memory-safe-assembly
assembly {
// Prepare the lookup table (skip the first "length" byte)
let tablePtr := add(table, 1)
// Prepare result pointer, jump over length
let resultPtr := add(result, 32)
// Run over the input, 3 bytes at a time
for {
let dataPtr := data
let endPtr := add(data, mload(data))
} lt(dataPtr, endPtr) {
} {
// Advance 3 bytes
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)
// To write each character, shift the 3 bytes (18 bits) chunk
// 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
// and apply logical AND with 0x3F which is the number of
// the previous character in the ASCII table prior to the Base64 Table
// The result is then added to the table to get the character to write,
// and finally write it in the result pointer but with a left shift
// of 256 (1 byte) - 8 (1 ASCII char) = 248 bits
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
}
// When data `bytes` is not exactly 3 bytes long
// it is padded with `=` characters at the end
switch mod(mload(data), 3)
case 1 {
mstore8(sub(resultPtr, 1), 0x3d)
mstore8(sub(resultPtr, 2), 0x3d)
}
case 2 {
mstore8(sub(resultPtr, 1), 0x3d)
}
}
return result;
}
}
contracts/mocks/OpenOceanExchangeMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../integration/interfaces/IOpenOceanCaller.sol";
library RevertReasonParser {
function parse(bytes memory data, string memory prefix) internal pure returns (string memory) {
// https://solidity.readthedocs.io/en/latest/control-structures.html#revert
// We assume that revert reason is abi-encoded as Error(string)
// 68 = 4-byte selector 0x08c379a0 + 32 bytes offset + 32 bytes length
if (data.length >= 68 && data[0] == "\x08" && data[1] == "\xc3" && data[2] == "\x79" && data[3] == "\xa0") {
string memory reason;
// solhint-disable no-inline-assembly
assembly {
// 68 = 32 bytes data length + 4-byte selector + 32 bytes offset
reason := add(data, 68)
}
/*
revert reason is padded up to 32 bytes with ABI encoder: Error(string)
also sometimes there is extra 32 bytes of zeros padded in the end:
https://github.com/ethereum/solidity/issues/10170
because of that we can't check for equality and instead check
that string length + extra 68 bytes is less than overall data length
*/
require(data.length >= 68 + bytes(reason).length, "Invalid revert reason");
return string(abi.encodePacked(prefix, "Error(", reason, ")"));
}
// 36 = 4-byte selector 0x4e487b71 + 32 bytes integer
else if (data.length == 36 && data[0] == "\x4e" && data[1] == "\x48" && data[2] == "\x7b" && data[3] == "\x71") {
uint256 code;
// solhint-disable no-inline-assembly
assembly {
// 36 = 32 bytes data length + 4-byte selector
code := mload(add(data, 36))
}
return string(abi.encodePacked(prefix, "Panic(", _toHex(code), ")"));
}
return string(abi.encodePacked(prefix, "Unknown()"));
}
function _toHex(uint256 value) private pure returns (string memory) {
return _toHex(abi.encodePacked(value));
}
function _toHex(bytes memory data) private pure returns (string memory) {
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(2 + data.length * 2);
str[0] = "0";
str[1] = "x";
for (uint256 i = 0; i < data.length; i++) {
str[2 * i + 2] = alphabet[uint8(data[i] >> 4)];
str[2 * i + 3] = alphabet[uint8(data[i] & 0x0f)];
}
return string(str);
}
}
library UniversalERC20 {
using SafeMath for uint256;
using SafeERC20 for IERC20;
IERC20 internal constant ZERO_ADDRESS = IERC20(0x0000000000000000000000000000000000000000);
IERC20 internal constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
IERC20 internal constant MATIC_ADDRESS = IERC20(0x0000000000000000000000000000000000001010);
function universalTransfer(IERC20 token, address payable to, uint256 amount) internal {
if (amount > 0) {
if (isETH(token)) {
(bool result, ) = to.call{value: amount}("");
require(result, "Failed to transfer ETH");
} else {
token.safeTransfer(to, amount);
}
}
}
function universalApprove(IERC20 token, address to, uint256 amount) internal {
require(!isETH(token), "Approve called on ETH");
if (amount == 0) {
token.forceApprove(to, 0);
} else {
uint256 allowance = token.allowance(address(this), to);
if (allowance < amount) {
if (allowance > 0) {
token.forceApprove(to, 0);
}
token.forceApprove(to, amount);
}
}
}
function universalBalanceOf(IERC20 token, address account) internal view returns (uint256) {
if (isETH(token)) {
return account.balance;
} else {
return token.balanceOf(account);
}
}
function isETH(IERC20 token) internal pure returns (bool) {
return
address(token) == address(ETH_ADDRESS) || address(token) == address(MATIC_ADDRESS) || address(token) == address(ZERO_ADDRESS);
}
}
contract OpenOceanCallerMock is IOpenOceanCaller {
address public token;
address public recipient;
uint256 public amount;
function __mock_setOutputResult(address token_, address recipient_, uint256 amount_) external {
token = token_;
recipient = recipient_;
amount = amount_;
}
function makeCall(CallDescription memory desc) external override {}
function makeCalls(CallDescription[] memory desc) external payable override {
IERC20(token).transfer(recipient, amount);
}
}
contract OpenOceanExchangeMock is OwnableUpgradeable, PausableUpgradeable {
using SafeMath for uint256;
using SafeERC20 for IERC20;
using UniversalERC20 for IERC20;
uint256 private constant _PARTIAL_FILL = 0x01;
uint256 private constant _SHOULD_CLAIM = 0x02;
struct SwapDescription {
IERC20 srcToken;
IERC20 dstToken;
address srcReceiver;
address dstReceiver;
uint256 amount;
uint256 minReturnAmount;
uint256 guaranteedAmount;
uint256 flags;
address referrer;
bytes permit;
}
event Swapped(
address indexed sender,
IERC20 indexed srcToken,
IERC20 indexed dstToken,
address dstReceiver,
uint256 amount,
uint256 spentAmount,
uint256 returnAmount,
uint256 minReturnAmount,
uint256 guaranteedAmount,
address referrer
);
function initialize() public initializer {
OwnableUpgradeable.__Ownable_init();
PausableUpgradeable.__Pausable_init();
}
function swap(
IOpenOceanCaller caller,
SwapDescription calldata desc,
IOpenOceanCaller.CallDescription[] calldata calls
) external payable whenNotPaused returns (uint256 returnAmount) {
require(desc.minReturnAmount > 0, "Min return should not be 0");
require(calls.length > 0, "Call data should exist");
uint256 flags = desc.flags;
IERC20 srcToken = desc.srcToken;
IERC20 dstToken = desc.dstToken;
require(msg.value == (srcToken.isETH() ? desc.amount : 0), "Invalid msg.value");
if (flags & _SHOULD_CLAIM != 0) {
require(!srcToken.isETH(), "Claim token is ETH");
_claim(srcToken, desc.srcReceiver, desc.amount, desc.permit);
}
address dstReceiver = (desc.dstReceiver == address(0)) ? msg.sender : desc.dstReceiver;
uint256 initialSrcBalance = (flags & _PARTIAL_FILL != 0) ? srcToken.universalBalanceOf(msg.sender) : 0;
uint256 initialDstBalance = dstToken.universalBalanceOf(dstReceiver);
caller.makeCalls{value: msg.value}(calls);
uint256 spentAmount = desc.amount;
returnAmount = dstToken.universalBalanceOf(dstReceiver).sub(initialDstBalance);
if (flags & _PARTIAL_FILL != 0) {
spentAmount = initialSrcBalance.add(desc.amount).sub(srcToken.universalBalanceOf(msg.sender));
require(returnAmount.mul(desc.amount) >= desc.minReturnAmount.mul(spentAmount), "Return amount is not enough");
} else {
require(returnAmount >= desc.minReturnAmount, "Return amount is not enough");
}
_emitSwapped(desc, srcToken, dstToken, dstReceiver, spentAmount, returnAmount);
}
function _emitSwapped(
SwapDescription calldata desc,
IERC20 srcToken,
IERC20 dstToken,
address dstReceiver,
uint256 spentAmount,
uint256 returnAmount
) private {
emit Swapped(
msg.sender,
srcToken,
dstToken,
dstReceiver,
desc.amount,
spentAmount,
returnAmount,
desc.minReturnAmount,
desc.guaranteedAmount,
desc.referrer
);
}
function _claim(IERC20 token, address dst, uint256 amount, bytes calldata permit) private {
// TODO: Is it safe to call permit on tokens without implemented permit? Fallback will be called. Is it bad for proxies?
if (permit.length == 32 * 7) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = address(token).call(abi.encodeWithSelector(IERC20Permit.permit.selector, permit));
if (!success) {
revert(RevertReasonParser.parse(result, "Permit call failed: "));
}
}
token.safeTransferFrom(msg.sender, dst, amount);
}
function rescueFunds(IERC20 token, uint256 amount) external onlyOwner {
token.universalTransfer(payable(msg.sender), amount);
}
function pause() external onlyOwner {
_pause();
}
}
contracts/dexV2/interfaces/IPairInfo.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IPairInfo {
function token0() external view returns (address);
function reserve0() external view returns (uint);
function decimals0() external view returns (uint);
function token1() external view returns (address);
function reserve1() external view returns (uint);
function decimals1() external view returns (uint);
function isPair(address _pair) external view returns (bool);
}
contracts/integration/interfaces/IMerklGaugeMiddleman.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title IMerklGaugeMiddleman Interface
* @dev Interface for the MerklGaugeMiddleman contract, which acts as an intermediary
* between Gauges and a DistributionCreator to manage reward distributions.
*/
interface IMerklGaugeMiddleman {
/**
* @dev Emitted when a gauge's parameters are set or updated.
* @param gauge Address of the gauge for which parameters are set
*/
event GaugeSet(address indexed gauge);
/**
* @dev Emitted when a distribution is created for a gauge.
* @param sender Address of the entity initiating the distribution
* @param gauge Address of the gauge for which the distribution is created
* @param amount The amount of tokens used from the gauge for distribution
* @param distributionAmount The total amount distributed to participants
*/
event CreateDistribution(address indexed sender, address indexed gauge, uint256 indexed amount, uint256 distributionAmount);
/// @dev Error thrown when the parameters provided to a function are invalid.
error InvalidParams();
/**
* @dev Notifies the contract about a reward for a specific gauge.
* This function is intended to be called by the gauge contract itself or an authorized entity.
* @param gauge_ Address of the gauge to notify about the reward
* @param amount_ Amount of reward tokens to be distributed
*/
function notifyReward(address gauge_, uint256 amount_) external;
/**
* @dev Transfers reward tokens from the caller and notifies the contract about a reward for a specific gauge.
* This combines the token transfer and notification into a single transaction for efficiency.
* @param gauge_ Address of the gauge to notify about the reward
* @param amount_ Amount of reward tokens to be transferred and then distributed
*/
function notifyRewardWithTransfer(address gauge_, uint256 amount_) external;
}
contracts/integration/interfaces/IPairIntegrationInfo.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IPairIntegrationInfo {
/// @notice The first of the two tokens of the pool, sorted by address
/// @return The token contract address
function token0() external view returns (address);
/// @notice The second of the two tokens of the pool, sorted by address
/// @return The token contract address
function token1() external view returns (address);
/// @notice The contract to which community fees are transferred
/// @return communityVaultAddress The communityVault address
function communityVault() external view returns (address);
}
@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)
pragma solidity ^0.8.0;
import "./IERC721Upgradeable.sol";
import "./IERC721ReceiverUpgradeable.sol";
import "./extensions/IERC721MetadataUpgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../utils/StringsUpgradeable.sol";
import "../../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
* the Metadata extension, but not including the Enumerable extension, which is available separately as
* {ERC721Enumerable}.
*/
contract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {
using AddressUpgradeable for address;
using StringsUpgradeable for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC721_init_unchained(name_, symbol_);
}
function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
_name = name_;
_symbol = symbol_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {
return
interfaceId == type(IERC721Upgradeable).interfaceId ||
interfaceId == type(IERC721MetadataUpgradeable).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721Upgradeable.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner or approved for all"
);
_approve(to, tokenId);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* `data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}
/**
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
*/
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _ownerOf(tokenId) != address(0);
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721Upgradeable.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/**
* @dev Safely mints `tokenId` and transfers it to `to`.
*
* Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId, 1);
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
require(!_exists(tokenId), "ERC721: token already minted");
unchecked {
// Will not overflow unless all 2**256 token ids are minted to the same owner.
// Given that tokens are minted one by one, it is impossible in practice that
// this ever happens. Might change if we allow batch minting.
// The ERC fails to describe this case.
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId, 1);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
* This is an internal function that does not check if the sender is authorized to operate on the token.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721Upgradeable.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId, 1);
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
owner = ERC721Upgradeable.ownerOf(tokenId);
// Clear approvals
delete _tokenApprovals[tokenId];
unchecked {
// Cannot overflow, as that would require more tokens to be burned/transferred
// out than the owner initially received through minting and transferring in.
_balances[owner] -= 1;
}
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId, 1);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal virtual {
require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId, 1);
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[from] -= 1;
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId, 1);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits an {Approval} event.
*/
function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Reverts if the `tokenId` has not been minted yet.
*/
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* The call is not executed if the target address is not a contract.
*
* @param from address representing the previous owner of the given token ID
* @param to target address that will receive the tokens
* @param tokenId uint256 ID of the token to be transferred
* @param data bytes optional data to send along with the call
* @return bool whether the call correctly returned the expected magic value
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
if (to.isContract()) {
try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721ReceiverUpgradeable.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
/**
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
* - When `from` is zero, the tokens will be minted for `to`.
* - When `to` is zero, ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
* - When `from` is zero, the tokens were minted for `to`.
* - When `to` is zero, ``from``'s tokens were burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
*
* WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
* being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
* that `ownerOf(tokenId)` is `a`.
*/
// solhint-disable-next-line func-name-mixedcase
function __unsafe_increaseBalance(address account, uint256 amount) internal {
_balances[account] += amount;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[44] private __gap;
}
contracts/gauges/GaugeProxy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import {IGaugeFactory} from "./interfaces/IGaugeFactory.sol";
contract GaugeProxy {
address private immutable gaugeFactory;
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
constructor() {
gaugeFactory = msg.sender;
}
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
function _setImplementation(address newImplementation) private {
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
fallback() external payable {
address impl = IGaugeFactory(gaugeFactory).gaugeImplementation();
require(impl != address(0));
//Just for etherscan compatibility
if (impl != _getImplementation() && msg.sender != (address(0))) {
_setImplementation(impl);
}
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
}
contracts/integration/interfaces/IOpenOceanExchange.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IOpenOceanCaller.sol";
interface IOpenOceanExchange {
struct SwapDescription {
IERC20 srcToken;
IERC20 dstToken;
address srcReceiver;
address dstReceiver;
uint256 amount;
uint256 minReturnAmount;
uint256 guaranteedAmount;
uint256 flags;
address referrer;
bytes permit;
}
function swap(
IOpenOceanCaller caller,
SwapDescription calldata desc,
IOpenOceanCaller.CallDescription[] calldata calls
) external payable returns (uint256 returnAmount);
}
contracts/lute/interfaces/ISingelTokenVirtualRewarder.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title Interface for Single Token Virtual Rewarder
* @dev Defines the basic interface for a reward system that handles deposits, withdrawals, and rewards based on token staking over different epochs.
*/
interface ISingelTokenVirtualRewarder {
/**
* @dev Emitted when a deposit is made.
* @param tokenId The identifier of the token being deposited.
* @param amount The amount of tokens deposited.
* @param epoch The epoch during which the deposit occurs.
*/
event Deposit(uint256 indexed tokenId, uint256 indexed amount, uint256 indexed epoch);
/**
* @dev Emitted when a withdrawal is made.
* @param tokenId The identifier of the token being withdrawn.
* @param amount The amount of tokens withdrawn.
* @param epoch The epoch during which the withdrawal occurs.
*/
event Withdraw(uint256 indexed tokenId, uint256 indexed amount, uint256 indexed epoch);
/**
* @dev Emitted when rewards are harvested.
* @param tokenId The identifier of the token for which rewards are harvested.
* @param rewardAmount The amount of rewards harvested.
* @param epochCount The epoch during which the harvest occurs.
*/
event Harvest(uint256 indexed tokenId, uint256 indexed rewardAmount, uint256 indexed epochCount);
/**
* @dev Emitted when a new reward amount is notified to be added to the pool.
* @param rewardAmount The amount of rewards added.
* @param epoch The epoch during which the reward is added.
*/
event NotifyReward(uint256 indexed rewardAmount, uint256 indexed epoch);
/**
* @notice Handles the deposit of tokens into the reward system.
* @param tokenId The identifier of the token being deposited.
* @param amount The amount of tokens to deposit.
*/
function deposit(uint256 tokenId, uint256 amount) external;
/**
* @notice Handles the withdrawal of tokens from the reward system.
* @param tokenId The identifier of the token being withdrawn.
* @param amount The amount of tokens to withdraw.
*/
function withdraw(uint256 tokenId, uint256 amount) external;
/**
* @notice Notifies the system of a new reward amount to be distributed.
* @param amount The amount of the new reward to add.
*/
function notifyRewardAmount(uint256 amount) external;
/**
* @notice Harvests rewards for a specific token.
* @param tokenId The identifier of the token for which to harvest rewards.
* @return reward The amount of harvested rewards.
*/
function harvest(uint256 tokenId) external returns (uint256 reward);
/**
* @notice Calculates the available amount of rewards for a specific token.
* @param tokenId The identifier of the token.
* @return reward The calculated reward amount.
*/
function calculateAvailableRewardsAmount(uint256 tokenId) external view returns (uint256 reward);
/**
* @notice Returns the strategy address associated with this contract.
* @return The address of the strategy.
*/
function strategy() external view returns (address);
/**
* @notice Returns the total supply of tokens under management.
* @return The total supply of tokens.
*/
function totalSupply() external view returns (uint256);
/**
* @notice Returns the balance of a specific token.
* @param tokenId The identifier of the token.
* @return The balance of the specified token.
*/
function balanceOf(uint256 tokenId) external view returns (uint256);
/**
* @notice Provides the balance of a specific tokenId at a given timestamp
*
* @param tokenId_ The ID of the token to check
* @param timestamp_ The specific timestamp to check the balance at
* @return The balance of the token at the given timestamp
*/
function balanceOfAt(uint256 tokenId_, uint256 timestamp_) external view returns (uint256);
/**
* @notice Provides the total supply of tokens at a given timestamp
*
* @param timestamp_ The timestamp to check the total supply at
* @return The total supply of tokens at the specified timestamp
*/
function totalSupplyAt(uint256 timestamp_) external view returns (uint256);
/**
* @notice Returns the reward per epoch for a specific epoch.
* @param epoch The epoch for which to retrieve the reward amount.
* @return The reward amount for the specified epoch.
*/
function rewardsPerEpoch(uint256 epoch) external view returns (uint256);
/**
* @notice Initializes the contract with necessary governance and operational addresses
* @dev Sets up operational aspects of the contract. This function can only be called once.
*
* @param strategy_ The strategy address that will interact with this contract
*/
function initialize(address strategy_) external;
}
@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721ReceiverUpgradeable {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
contracts/mocks/ERC20Faucet.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract ERC20Faucet is ERC20, Ownable {
uint8 internal _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
_decimals = decimals_;
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
function faucet() external payable {
if (msg.value > 0.01 ether) {
_mint(msg.sender, 1000 * (10 ** _decimals));
}
}
function withdraw() external onlyOwner {
payable(msg.sender).transfer(address(this).balance);
}
function mint(address to_, uint256 amount_) external onlyOwner {
_mint(to_, amount_);
}
}
contracts/mocks/Timelock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
contracts/mocks/BribeFactoryMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {BribeFactoryUpgradeable} from "../bribes/BribeFactoryUpgradeable.sol";
interface IBribeMock {
function setVoter(address _voter) external;
}
contract BribeFactoryMock is BribeFactoryUpgradeable {
function setImplementation(address _implementation) external {
_checkAddressZero(_implementation);
require(_implementation != address(0));
emit bribeImplementationChanged(bribeImplementation, _implementation);
bribeImplementation = _implementation;
}
function setVoterToBribe(address _bribe, address _voter) external {
IBribeMock(_bribe).setVoter(_voter);
}
}
contracts/gauges/GaugeFactoryUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IGaugeFactory} from "./interfaces/IGaugeFactory.sol";
import {IGauge} from "./interfaces/IGauge.sol";
import {GaugeProxy} from "./GaugeProxy.sol";
contract GaugeFactoryUpgradeable is IGaugeFactory, OwnableUpgradeable {
address public last_gauge;
address public voter;
address public override gaugeImplementation;
address public override merklGaugeMiddleman;
error AddressZero();
constructor() {
_disableInitializers();
}
function initialize(address _voter, address _gaugeImplementation, address _merklGaugeMiddleman) external initializer {
_checkAddressZero(_voter);
_checkAddressZero(_gaugeImplementation);
__Ownable_init();
voter = _voter;
gaugeImplementation = _gaugeImplementation;
merklGaugeMiddleman = _merklGaugeMiddleman;
}
function createGauge(
address _rewardToken,
address _ve,
address _token,
address _distribution,
address _internal_bribe,
address _external_bribe,
bool _isDistributeEmissionToMerkle,
address _feeVault
) external virtual override returns (address) {
require(msg.sender == voter || msg.sender == owner(), "only voter or owner");
address newLastGauge = address(new GaugeProxy());
IGauge(newLastGauge).initialize(
_rewardToken,
_ve,
_token,
_distribution,
_internal_bribe,
_external_bribe,
_isDistributeEmissionToMerkle,
merklGaugeMiddleman,
_feeVault
);
last_gauge = newLastGauge;
return newLastGauge;
}
function gaugeOwner() external view returns (address) {
return owner();
}
function changeImplementation(address _implementation) external onlyOwner {
_checkAddressZero(_implementation);
emit GaugeImplementationChanged(gaugeImplementation, _implementation);
gaugeImplementation = _implementation;
}
function setMerklGaugeMiddleman(address _newMerklGaugeMiddleman) external onlyOwner {
merklGaugeMiddleman = _newMerklGaugeMiddleman;
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts/utils/Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
@openzeppelin/contracts/interfaces/draft-IERC1822.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}
contracts/core/interfaces/IMinter.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IMinter {
event Mint(address indexed sender, uint256 weekly, uint256 circulating_supply);
event Emission(
uint256 indexed epoch,
int256 epochAdjustmentBps,
uint256 standardEmission,
uint256 adjustedEmission,
uint256 teamAmount,
uint256 gaugeAmount
);
event SetEpochEmissionAdjustmentBps(
int256 epochEmissionAdjustmentBps
);
function update_period() external returns (uint);
function check() external view returns (bool);
function period() external view returns (uint);
function active_period() external view returns (uint);
}
contracts/lute/CompoundVeLUTEManagedNFTStrategyFactoryUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ICompoundVeLUTEManagedNFTStrategyFactory} from "./interfaces/ICompoundVeLUTEManagedNFTStrategyFactory.sol";
import {ISingelTokenVirtualRewarder} from "./interfaces/ISingelTokenVirtualRewarder.sol";
import {ICompoundVeLUTEManagedNFTStrategy} from "./interfaces/ICompoundVeLUTEManagedNFTStrategy.sol";
import {StrategyProxy} from "./StrategyProxy.sol";
import {VirtualRewarderProxy} from "./VirtualRewarderProxy.sol";
/**
* @title Factory for Compound VeLUTE Managed NFT Strategies and Virtual Rewarders
* @notice This contract serves as a factory for creating and initializing managed NFT strategies and their corresponding virtual rewarders in the Compound VeLUTE ecosystem.
* It uses proxy contracts for strategy and rewarder creation to ensure upgradability.
*/
contract CompoundVeLUTEManagedNFTStrategyFactoryUpgradeable is
ICompoundVeLUTEManagedNFTStrategyFactory,
AccessControlUpgradeable
{
/**
* @notice Role identifier used for granting permissions to create new strategies.
*/
bytes32 public constant STRATEGY_CREATOR_ROLE = keccak256("STRATEGY_CREATOR_ROLE");
/**
* @notice Address of the current strategy implementation used for creating new strategies
*/
address public override strategyImplementation;
/**
* @notice Address of the current virtual rewarder implementation used for creating new rewarders
*/
address public override virtualRewarderImplementation;
/**
* @notice Address of the managed NFT manager interacting with the strategies
*/
address public override managedNFTManager;
/**
* @notice The address of the Router V2 Path Provider used to fetch and calculate optimal routes for token transactions within strategies.
*/
address public override routerV2PathProvider;
error AddressZero();
/**
* @dev Constructor that disables initialization on implementation.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the factory with the necessary addresses and default configuration.
* @dev Sets up the contract with initial governance settings, roles, and implementations for strategies and rewarders. Marks the contract as initialized.
* @param strategyImplementation_ The initial implementation contract for strategies.
* @param virtualRewarderImplementation_ The initial implementation contract for virtual rewarders.
* @param managedNFTManager_ The manager address for interacting with managed NFTs.
* @param routerV2PathProvider_ The address of the router V2 path provider used for fetching and calculating optimal token swap routes.
*/
function initialize(
address strategyImplementation_,
address virtualRewarderImplementation_,
address managedNFTManager_,
address routerV2PathProvider_
) external initializer {
_checkAddressZero(strategyImplementation_);
_checkAddressZero(virtualRewarderImplementation_);
_checkAddressZero(managedNFTManager_);
_checkAddressZero(routerV2PathProvider_);
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(STRATEGY_CREATOR_ROLE, msg.sender);
strategyImplementation = strategyImplementation_;
virtualRewarderImplementation = virtualRewarderImplementation_;
managedNFTManager = managedNFTManager_;
routerV2PathProvider = routerV2PathProvider_;
}
/**
* @notice Creates a new strategy and corresponding virtual rewarder with a specified name
* @dev Requires the caller to have the STRATEGY_CREATOR_ROLE
* @param name_ Descriptive name for the new strategy
* @return The address of the newly created strategy instance
*/
function createStrategy(string calldata name_) external override onlyRole(STRATEGY_CREATOR_ROLE) returns (address) {
ICompoundVeLUTEManagedNFTStrategy strategy = ICompoundVeLUTEManagedNFTStrategy(address(new StrategyProxy()));
ISingelTokenVirtualRewarder virtualRewarder = ISingelTokenVirtualRewarder(address(new VirtualRewarderProxy()));
strategy.initialize(managedNFTManager, address(virtualRewarder), routerV2PathProvider, name_);
virtualRewarder.initialize(address(strategy));
emit CreateStrategy(address(strategy), address(virtualRewarder), name_);
return address(strategy);
}
/**
* @notice Updates the implementation address for virtual rewarders
* @dev Only accessible by admins with DEFAULT_ADMIN_ROLE
* @param virtualRewarderImplementation_ New implementation address for virtual rewarders
*/
function changeVirtualRewarderImplementation(address virtualRewarderImplementation_) external onlyRole(DEFAULT_ADMIN_ROLE) {
_checkAddressZero(virtualRewarderImplementation_);
emit ChangeVirtualRewarderImplementation(virtualRewarderImplementation, virtualRewarderImplementation_);
virtualRewarderImplementation = virtualRewarderImplementation_;
}
/**
* @notice Updates the implementation address for strategies
* @dev Only accessible by admins with DEFAULT_ADMIN_ROLE
*
* @param strategyImplementation_ New implementation address for strategies
*/
function changeStrategyImplementation(address strategyImplementation_) external onlyRole(DEFAULT_ADMIN_ROLE) {
_checkAddressZero(strategyImplementation_);
emit ChangeStrategyImplementation(strategyImplementation, strategyImplementation_);
strategyImplementation = strategyImplementation_;
}
/**
* @notice Sets a new address for the Router V2 Path Provider.
* @dev Accessible only by admins, this function updates the address used for determining swap routes in token buyback strategies.
* @param routerV2PathProvider_ The new Router V2 Path Provider address.
*/
function setRouterV2PathProvider(address routerV2PathProvider_) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_checkAddressZero(routerV2PathProvider_);
emit SetRouterV2PathProvider(routerV2PathProvider, routerV2PathProvider_);
routerV2PathProvider = routerV2PathProvider_;
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/core/interfaces/IRLute.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title IRLute Interface
* @dev Interface for the RLute token contract.
* Provides the necessary declarations for interacting with the RLute functionalities.
*/
interface IRLute {
/// @notice Emitted when rLUTE tokens are converted to LUTE and veLUTE tokens.
/// @param sender The address that initiated the conversion.
/// @param amount The amount of rLUTE tokens converted.
/// @param toTokenAmount The amount of LUTE tokens received from the conversion.
/// @param toVeNFTAmount The amount of veLUTE tokens received from the conversion.
/// @param tokenId The ID of the veLUTE token received, if applicable.
event Converted(address indexed sender, uint256 amount, uint256 toTokenAmount, uint256 toVeNFTAmount, uint256 tokenId);
/// @notice Emitted when LUTE tokens are recovered from the contract.
/// @param sender The address that performed the recovery.
/// @param amount The amount of LUTE tokens recovered.
event Recover(address indexed sender, uint256 amount);
/// @dev Reverts if the attempted operation involves zero amount.
error ZERO_AMOUNT();
/**
* @notice Converts all rLUTE tokens of the caller to LUTE and veLUTE tokens.
*/
function convertAll() external;
/**
* @notice Converts a specific amount of rLUTE tokens to LUTE and veLUTE tokens.
* @param amount_ The amount of rLUTE tokens to be converted.
*/
function convert(uint256 amount_) external;
/**
* @notice Allows the contract owner to recover LUTE tokens from the contract.
* @param amount_ The amount of LUTE tokens to be recovered.
*/
function recoverToken(uint256 amount_) external;
/**
* @notice Mints rLUTE tokens to a specified address.
* @param to_ The address that will receive the minted rLUTE tokens.
* @param amount_ The amount of rLUTE tokens to mint.
*/
function mint(address to_, uint256 amount_) external;
/**
* @notice Returns the address of the LUTE token.
* @return address The LUTE token contract address.
*/
function token() external view returns (address);
/**
* @notice Returns the address of the Voting Escrow contract.
* @return address The Voting Escrow contract address.
*/
function votingEscrow() external view returns (address);
}
contracts/gauges/interfaces/IRewardReciever.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
interface IRewardReciever {
function notifyRewardAmount(address token_, uint256 rewardAmount_) external;
}
contracts/core/VeArtProxyAlandale.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {Base64Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/Base64Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import {LibVotingEscrowUtils} from "./libraries/LibVotingEscrowUtils.sol";
import {DateTime} from "./libraries/DateTime.sol";
import {NumberFormatter, Strings} from "./libraries/NumberFormatter.sol";
import {IVeArtProxy} from "./interfaces/IVeArtProxy.sol";
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
import {IManagedNFTManager} from "../lute/interfaces/IManagedNFTManager.sol";
import {ICompoundVeLUTEManagedNFTStrategy} from "../lute/interfaces/ICompoundVeLUTEManagedNFTStrategy.sol";
/**
* @title VeArtProxyAlandale
* @notice Alandale "week seal" card. Template by the Alandale design team; six live
* slots are filled per render: locked amount, voting power, unlock date, status,
* seal-ring progress mask, and token id. Fully self-contained (no static proxy).
*/
contract VeArtProxyAlandale is IVeArtProxy {
using DateTime for uint256;
using Strings for uint256;
uint256 internal constant MAX_LOCK = 15724800; // mirrors LibVotingEscrowConstants
address public immutable votingEscrow;
address public immutable managedNftManager;
string private constant _S0 = unicode"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 600 600\" font-family=\"ui-monospace,Menlo,Consolas,'Courier New',monospace\"><defs><linearGradient id=\"luteGradC\" x1=\"613.48\" y1=\"126.03\" x2=\"205.48\" y2=\"682.03\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0\" stop-color=\"#4fd1a0\"></stop><stop offset=\".5\" stop-color=\"#1c8c64\"></stop><stop offset=\"1\" stop-color=\"#1b2320\"></stop></linearGradient><pattern id=\"gridC\" width=\"30\" height=\"30\" patternUnits=\"userSpaceOnUse\"><path d=\"M30 0H0V30\" fill=\"none\" stroke=\"#212B26\" stroke-width=\"1\"></path></pattern><radialGradient id=\"sealGlow\" cx=\"0.5\" cy=\"0.5\" r=\"0.5\"><stop offset=\"0\" stop-color=\"#1C8C64\" stop-opacity=\"0.25\"></stop><stop offset=\"1\" stop-color=\"#1C8C64\" stop-opacity=\"0\"></stop></radialGradient><path id=\"ringTextC\" d=\"M300 128 a172 172 0 1 1 -0.1 0\"></path><linearGradient id=\"brandC\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#4FD1A0\"></stop><stop offset=\"1\" stop-color=\"#1C8C64\"></stop></linearGradient><radialGradient id=\"plateC\" cx=\"0.35\" cy=\"0.3\" r=\"0.95\"><stop offset=\"0\" stop-color=\"#25312A\"></stop><stop offset=\"1\" stop-color=\"#121815\"></stop></radialGradient></defs><rect width=\"600\" height=\"600\" fill=\"#1B2320\"></rect><rect width=\"600\" height=\"600\" fill=\"url(#gridC)\"></rect><radialGradient id=\"etherC\" cx=\"0.5\" cy=\"0.5\" r=\"0.5\"><stop offset=\"0\" stop-color=\"#4FD1A0\" stop-opacity=\"0.11\"/><stop offset=\"1\" stop-color=\"#4FD1A0\" stop-opacity=\"0\"/></radialGradient><circle cx=\"150\" cy=\"180\" r=\"230\" fill=\"url(#etherC)\"><animateTransform attributeName=\"transform\" type=\"translate\" values=\"0 0; 160 90; 40 230; -70 60; 0 0\" dur=\"38s\" repeatCount=\"indefinite\"/></circle><circle cx=\"470\" cy=\"440\" r=\"260\" fill=\"url(#etherC)\" opacity=\"0.75\"><animateTransform attributeName=\"transform\" type=\"translate\" values=\"0 0; -150 -80; -40 -210; 90 -50; 0 0\" dur=\"49s\" repeatCount=\"indefinite\"/></circle><rect x=\"10\" y=\"10\" width=\"580\" height=\"580\" fill=\"none\" stroke=\"#2A3630\" stroke-width=\"1.5\"></rect><path d=\"M10 34V10H34 M566 10H590V34 M590 566V590H566 M34 590H10V566\" fill=\"none\" stroke=\"url(#brandC)\" stroke-width=\"2\"></path><text x=\"36\" y=\"46\" fill=\"#8FAF9F\" font-size=\"10\" letter-spacing=\"2\">LOCKED AMOUNT</text><text x=\"36\" y=\"70\" fill=\"#F2F7F4\" font-size=\"16\">";
string private constant _S1 = unicode" LUTE</text><text x=\"564\" y=\"46\" fill=\"#8FAF9F\" font-size=\"10\" letter-spacing=\"2\" text-anchor=\"end\">VOTING POWER</text><text x=\"564\" y=\"70\" fill=\"#F2F7F4\" font-size=\"16\" text-anchor=\"end\">";
string private constant _S2 = unicode"</text><text x=\"36\" y=\"540\" fill=\"#8FAF9F\" font-size=\"10\" letter-spacing=\"2\">UNLOCKS</text><text x=\"36\" y=\"564\" fill=\"#F2F7F4\" font-size=\"16\">";
string private constant _S3 = unicode"</text><text x=\"564\" y=\"540\" fill=\"#8FAF9F\" font-size=\"10\" letter-spacing=\"2\" text-anchor=\"end\">STATUS</text><text x=\"564\" y=\"564\" fill=\"#4FD1A0\" font-size=\"16\" text-anchor=\"end\">";
string private constant _S4 = unicode"</text><circle cx=\"300\" cy=\"300\" r=\"210\" fill=\"url(#sealGlow)\"></circle><g><g><animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 300 300\" to=\"360 300 300\" dur=\"240s\" repeatCount=\"indefinite\"/><text fill=\"#7E9C8D\" font-size=\"11\" letter-spacing=\"5.5\"><textPath href=\"#ringTextC\">ALANDALE · VOTING ESCROW · WHERE THE LOOT FLOWS · veLUTE</textPath></text></g></g><circle cx=\"300\" cy=\"300\" r=\"163\" fill=\"none\" stroke=\"#1C8C64\" stroke-width=\"1\" opacity=\"0.6\"></circle><circle cx=\"300\" cy=\"300\" r=\"152\" fill=\"none\" stroke=\"url(#brandC)\" stroke-width=\"14\" pathLength=\"52\" stroke-dasharray=\"0.45 0.55\" transform=\"rotate(-90 300 300)\"></circle><circle cx=\"300\" cy=\"300\" r=\"152\" fill=\"none\" stroke=\"#1B2320\" stroke-width=\"18\" pathLength=\"100\" stroke-dasharray=\"";
string private constant _S5 = unicode" 100\" transform=\"rotate(-90 300 300)\"></circle><circle cx=\"300\" cy=\"300\" r=\"152\" fill=\"none\" stroke=\"#2C3831\" stroke-width=\"1\" pathLength=\"52\" stroke-dasharray=\"0.45 0.55\" transform=\"rotate(-90 300 300)\" opacity=\"0.9\"></circle><circle cx=\"300\" cy=\"300\" r=\"141\" fill=\"none\" stroke=\"#1C8C64\" stroke-width=\"1\" opacity=\"0.6\"></circle><path d=\"M300 122l7 10-7 10-7-10z\" fill=\"#4FD1A0\"><animate attributeName=\"opacity\" values=\"1;0.4;1\" dur=\"4s\" repeatCount=\"indefinite\"/></path><circle cx=\"300\" cy=\"300\" r=\"120\" fill=\"url(#plateC)\" stroke=\"#1C8C64\" stroke-width=\"1.5\"></circle><g transform=\"translate(208.35,209.5) scale(0.235)\"><path fill=\"url(#luteGradC)\" d=\"M187.89,562.26c-28-60.15-17.6-134.88,23.85-186.37,33.16-41.92,81.77-68,128.05-92.89,27.48-14.35,55.39-27.99,83.52-41.02,6.9-3.14,13.87-6.61,20.74-9.83,5.14-2.81,11.97-4.13,15.14-9.53,14.37-24.77,27.92-50.2,42.25-74.99,5.91-10.86,18.16-15.09,28.45-20.67,10.82-5.43,21.75-10.87,32.51-16.41,7.17-3.68,14.65-7.33,21.73-11.19,6.2-2.54,16.3-11.36,20.87-2.75,2.05,17.25.17,49.21.72,66.54-.9,5.15-4.47,9.81-8.88,12.52-14.88,7.62-31.36,12.44-45.76,20.88-6.62,8.62-11.49,18.64-16.93,28.04-9.25,15.92-17.88,32.26-27.05,48.22-2.27,9.77,2.49,20.66,3.14,30.69,2.43,14.95,4.75,30.16,6.83,45.17,8.99,78.34,24.42,167.93-22.92,237.55-76.9,115.91-247.5,101.18-306.26-23.93ZM472.37,461.73c.5-27.93-.78-63.4-4.45-90.77-1.36-3.67-.47-28.06-5.6-19.31-14.17,25.19-28.91,50.12-42.85,75.43-22.01,30.93,5.16,37.67-8.93,77.46-17.29,48.31-85.54,56.63-112.38,12.19-16.82-25.54-11.44-62.36,11.27-82.63,11.51-11.86,29.08-13.72,40.76-24.42,15.51-21.9,26.89-46.77,40.79-69.72,5.91-11.23,13.7-21.91,18.81-33.44-3.18-.83-6.99,2.82-10,4-5.47,2.95-11.03,6.04-16.48,9.03-36.4,20.3-73.97,40.02-105.13,68.07-61.57,53.5-59.48,151.89,12.07,195.51,49.86,32.78,119.65,16.18,154.13-31.13,19.78-25.77,26.01-58.57,28-90.27Z\"></path></g><text x=\"300\" y=\"588\" fill=\"#4FD1A0\" font-size=\"13\" letter-spacing=\"3\" text-anchor=\"middle\">SEAL Nº ";
string private constant _S6 = unicode"</text></svg>";
constructor(address votingEscrow_, address managedNftManager_) {
votingEscrow = votingEscrow_;
managedNftManager = managedNftManager_;
}
function tokenURI(uint256 tokenId_) external view virtual override returns (string memory) {
IVotingEscrow ve = IVotingEscrow(votingEscrow);
IVotingEscrow.TokenState memory state = ve.getNftState(tokenId_);
uint256 balance;
uint256 votingPower;
uint256 lockedEnd = (state.isAttached || state.locked.isPermanentLocked)
? LibVotingEscrowUtils.maxUnlockTimestamp()
: state.locked.end;
if (state.isAttached) {
address strategy = IERC721Upgradeable(address(ve)).ownerOf(
IManagedNFTManager(managedNftManager).getAttachedManagedTokenId(tokenId_)
);
balance = ICompoundVeLUTEManagedNFTStrategy(strategy).balanceOf(tokenId_);
votingPower = balance;
} else {
balance = uint256(int256(state.locked.amount));
votingPower = ve.balanceOfNftIgnoreOwnershipChange(tokenId_);
}
string memory svg = string.concat(
_S0,
NumberFormatter.formatNumber(balance, 18, 2),
_S1,
NumberFormatter.formatNumber(votingPower, 18, 2),
_S2,
_toDateString(lockedEnd),
_S3,
_status(state, ve.isTransferable(tokenId_))
);
svg = string.concat(svg, _S4, _maskPct(state, lockedEnd), _S5, tokenId_.toString(), _S6);
return string.concat(
"data:application/json;base64,",
Base64Upgradeable.encode(
bytes(
string.concat(
unicode'{"name": "veLUTE Seal Nº ',
tokenId_.toString(),
'", "description": "Alandale locks, can be used to boost gauge yields, vote on token emission, and receive bribes", "image": "data:image/svg+xml;base64,',
Base64Upgradeable.encode(bytes(svg)),
'"}'
)
)
)
);
}
function _status(IVotingEscrow.TokenState memory state_, bool transferable_) internal pure returns (string memory) {
if (state_.isAttached) return "ATTACHED";
if (state_.locked.isPermanentLocked) return "PERMANENT";
return transferable_ ? "TRANSFERABLE" : "RESTRICTED";
}
/// @dev share of the seal ring masked out = elapsed share of the lock (0 for permanent/attached)
function _maskPct(IVotingEscrow.TokenState memory state_, uint256 lockedEnd_) internal view returns (string memory) {
if (state_.isAttached || state_.locked.isPermanentLocked) return "0";
if (lockedEnd_ <= block.timestamp) return "100";
uint256 remaining = lockedEnd_ - block.timestamp;
if (remaining >= MAX_LOCK) return "0";
return ((MAX_LOCK - remaining) * 100 / MAX_LOCK).toString();
}
function _toDateString(uint256 timestamp_) internal pure returns (string memory) {
(uint256 year, uint256 month, uint256 day) = timestamp_.timestampToDate();
string[12] memory monthNames = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
return string.concat(day.toString(), " ", monthNames[month - 1], " ", year.toString());
}
}
contracts/utils/RewardAPIUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {
IERC20Upgradeable,
IERC20MetadataUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol";
import "../core/interfaces/IVoter.sol";
import "../core/interfaces/IVotingEscrow.sol";
import "../dexV2/interfaces/IPairFactory.sol";
import "../dexV2/interfaces/IPair.sol";
import "../gauges/interfaces/IGauge.sol";
import "../bribes/interfaces/IBribe.sol";
interface IExtendVoter is IVoter {
function pools(uint256 index) external view returns (address);
function totalWeightsPerEpoch(uint256 epoch) external view returns (uint256);
}
contract RewardAPIUpgradeable is OwnableUpgradeable {
IPairFactory public pairFactory;
IVoter public voter;
address public underlyingToken;
uint256 public constant MAX_PAIRS = 1000;
mapping(address => bool) public notReward;
function initialize(address _voter) public initializer {
__Ownable_init();
voter = IVoter(_voter);
pairFactory = IPairFactory(voter.v2PoolFactory());
underlyingToken = IVotingEscrow(voter.votingEscrow()).token();
address _notrew = address(0x0000000000000000000000000000000000000000);
notReward[_notrew] = true;
}
function addNotReward(address _token) external onlyOwner {
notReward[_token] = true;
}
function removeNotReward(address _token) external onlyOwner {
notReward[_token] = false;
}
function setVoter(address _voter) external onlyOwner {
require(_voter != address(0), "zeroAddr");
voter = IVoter(_voter);
// update variable depending on voter
pairFactory = IPairFactory(voter.v2PoolFactory());
underlyingToken = IVotingEscrow(voter.votingEscrow()).token();
}
struct PairRewards {
address _pool;
address _gauge;
address _externalBribeAddress;
address _internalBribeAddress;
uint256 emissionReward;
uint totalVotesOnGauge; // Weight of votes of a pair Eg 12000veCHR voted
uint totalVotesOnGaugeByUser; // Weight of votes of a pair from the user Eg you voted 1200veCHR on this pair
Bribes externalBribeReward;
Bribes internalBribeReward;
}
struct Bribes {
address[] tokens;
string[] symbols;
uint[] decimals;
uint[] amounts;
address bribe;
}
struct Rewards {
Bribes[] bribes;
}
// @Notice Get the rewards available the next epoch.
function getExpectedClaimForNextEpoch(uint tokenId, address[] memory pairs) external view returns (Rewards[] memory) {
uint i;
uint len = pairs.length;
address _gauge;
address _bribe;
Rewards[] memory _rewards = new Rewards[](len);
//external
for (i = 0; i < len; i++) {
Bribes[] memory _tempReward = new Bribes[](2);
_gauge = voter.poolToGauge(pairs[i]);
IVoter.GaugeState memory state = voter.getGaugeState(_gauge);
// get external
_bribe = state.externalBribe;
_tempReward[0] = _getEpochRewards(tokenId, _bribe);
// get internal
_bribe = state.internalBribe;
_tempReward[1] = _getEpochRewards(tokenId, _bribe);
_rewards[i].bribes = _tempReward;
}
return _rewards;
}
struct Claimable {
address bribe;
address[] tokens;
string[] symbols;
uint[] decimals;
uint[] amounts;
}
function getAvailableRewards(
uint tokenId,
address[] memory _bribes,
address[][] memory _tokens
) public view returns (Claimable[] memory toCLaim) {
address _bribe;
uint len = _bribes.length;
Claimable[] memory _toClaim = new Claimable[](len);
uint len2;
uint amount;
address _token;
for (uint i; i < len; i++) {
_bribe = _bribes[i];
len2 = _tokens[i].length;
address[] memory _tokensReward = new address[](len2);
uint[] memory _amounts = new uint[](len2);
for (uint u; u < len2; u++) {
_token = _tokens[i][u];
amount = IBribe(_bribe).earned(tokenId, _token);
if (amount != 0) {
_tokensReward[u] = _token;
_amounts[u] = amount;
}
}
_toClaim[i].bribe = _bribe;
_toClaim[i].tokens = _tokensReward;
_toClaim[i].amounts = _amounts;
}
return _toClaim;
}
function getAllPairRewards(address _user, uint _amounts, uint _offset) external view returns (PairRewards[] memory Pairs) {
require(_amounts <= MAX_PAIRS, "too many pair");
Pairs = new PairRewards[](_amounts);
uint i = _offset;
uint totPairs = pairFactory.allPairsLength();
address _pair;
address _gauge;
address _bribe;
uint j = 0;
uint time = voter.epochTimestamp();
address votingEscrow = voter.votingEscrow();
for (i; i < _offset + _amounts; i++) {
// if totalPairs is reached, break.
if (i == totPairs) {
break;
}
_pair = pairFactory.allPairs(i);
_gauge = voter.poolToGauge(_pair);
Pairs[j]._pool = _pair;
if (_gauge != address(0)) {
Pairs[j]._gauge = _gauge;
Pairs[j].totalVotesOnGauge = voter.weightsPerEpoch(time, _pair);
if (_user != address(0)) {
uint256 userTokensBalance = IERC721EnumerableUpgradeable(votingEscrow).balanceOf(_user);
for (uint256 u = 0; u < userTokensBalance; u++) {
uint256 tokenId = IERC721EnumerableUpgradeable(votingEscrow).tokenOfOwnerByIndex(_user, u);
if (voter.lastVotedTimestamps(tokenId) >= time) {
Pairs[j].totalVotesOnGaugeByUser += voter.votes(tokenId, _pair);
}
}
}
IVoter.GaugeState memory state = voter.getGaugeState(_gauge);
Pairs[j].emissionReward = IGauge(_gauge).earned(_user);
// get external
_bribe = state.externalBribe;
Pairs[j]._externalBribeAddress = _bribe;
//Pairs[j].externalBribeReward = _getNextEpochRewards(_bribe);
// get internal
_bribe = state.internalBribe;
Pairs[j]._internalBribeAddress = _bribe;
//Pairs[j].internalBribeReward = _getNextEpochRewards(_bribe);
}
j++;
}
}
function getAllCLPairRewards(address _user, uint _amounts, uint _offset) external view returns (PairRewards[] memory Pairs) {
require(_amounts <= MAX_PAIRS, "too many pair");
Pairs = new PairRewards[](_amounts);
uint i = _offset;
(, , uint totPairs) = voter.poolsCounts();
address _pair;
address _gauge;
address _bribe;
uint time = voter.epochTimestamp();
address votingEscrow = voter.votingEscrow();
uint j = 0;
for (i; i < _offset + _amounts; i++) {
// if totalPairs is reached, break.
if (i == totPairs) {
break;
}
_pair = voter.v3Pools(i);
_gauge = voter.poolToGauge(_pair);
Pairs[j]._pool = _pair;
if (_gauge != address(0)) {
Pairs[j]._gauge = _gauge;
Pairs[j].totalVotesOnGauge = voter.weightsPerEpoch(time, _pair);
if (_user != address(0)) {
uint256 userTokensBalance = IERC721EnumerableUpgradeable(voter.votingEscrow()).balanceOf(_user);
for (uint u = 0; u < userTokensBalance; u++) {
uint256 tokenId = IERC721EnumerableUpgradeable(votingEscrow).tokenOfOwnerByIndex(_user, u);
if (voter.lastVotedTimestamps(tokenId) >= time) {
Pairs[j].totalVotesOnGaugeByUser += voter.votes(tokenId, _pair);
}
}
}
IVoter.GaugeState memory state = voter.getGaugeState(_gauge);
Pairs[j].emissionReward = IGauge(_gauge).earned(_user);
// get external
_bribe = state.externalBribe;
Pairs[j]._externalBribeAddress = _bribe;
//Pairs[j].externalBribeReward = _getNextEpochRewards(_bribe);
// get internal
_bribe = state.internalBribe;
Pairs[j]._internalBribeAddress = _bribe;
//Pairs[j].internalBribeReward = _getNextEpochRewards(_bribe);
}
j++;
}
}
function _getEpochRewards(uint tokenId, address _bribe) internal view returns (Bribes memory _rewards) {
IBribe bribe = IBribe(_bribe);
uint ts = bribe.getEpochStart();
uint _balance = bribe.balanceOfAt(tokenId, ts);
if (_balance == 0) {
_rewards.bribe = _bribe;
return _rewards;
}
address[] memory rewardTokens = bribe.getRewardTokens();
uint[] memory _amounts = new uint[](rewardTokens.length);
address[] memory _tokens = new address[](rewardTokens.length);
string[] memory _symbol = new string[](rewardTokens.length);
uint[] memory _decimals = new uint[](rewardTokens.length);
uint i = 0;
uint _supply = bribe.totalSupplyAt(ts);
address _token;
for (i; i < rewardTokens.length; i++) {
_token = rewardTokens[i];
_tokens[i] = _token;
if (_balance == 0 || notReward[_token]) {
_amounts[i] = 0;
_symbol[i] = "";
_decimals[i] = 0;
} else {
_symbol[i] = IERC20MetadataUpgradeable(_token).symbol();
_decimals[i] = IERC20MetadataUpgradeable(_token).decimals();
(, uint256 rewardsPerEpoch, ) = bribe.rewardData(_token, ts);
_amounts[i] = (((rewardsPerEpoch * 1e18) / _supply) * _balance) / 1e18;
}
}
_rewards.tokens = _tokens;
_rewards.amounts = _amounts;
_rewards.symbols = _symbol;
_rewards.decimals = _decimals;
_rewards.bribe = _bribe;
}
// read all the bribe available for a pair
function getPairBribe(address pair) public view returns (Bribes[] memory) {
address _gauge;
address _bribe;
Bribes[] memory _tempReward = new Bribes[](2);
_gauge = voter.poolToGauge(pair);
IVoter.GaugeState memory state = voter.getGaugeState(_gauge);
// get external
_bribe = state.externalBribe;
_tempReward[0] = _getNextEpochRewards(_bribe);
// get internal
_bribe = state.internalBribe;
_tempReward[1] = _getNextEpochRewards(_bribe);
return _tempReward;
}
function _getNextEpochRewards(address _bribe) internal view returns (Bribes memory _rewards) {
address[] memory rewardTokens = IBribe(_bribe).getRewardTokens();
uint[] memory _amounts = new uint[](rewardTokens.length);
address[] memory _tokens = new address[](rewardTokens.length);
string[] memory _symbol = new string[](rewardTokens.length);
uint[] memory _decimals = new uint[](rewardTokens.length);
uint ts = IBribe(_bribe).getNextEpochStart();
uint i = 0;
address _token;
for (i; i < rewardTokens.length; i++) {
_token = rewardTokens[i];
_tokens[i] = _token;
if (notReward[_token]) {
_amounts[i] = 0;
_tokens[i] = address(0);
_symbol[i] = "";
_decimals[i] = 0;
} else {
_symbol[i] = IERC20MetadataUpgradeable(_token).symbol();
_decimals[i] = IERC20MetadataUpgradeable(_token).decimals();
(, uint256 rewardsPerEpoch, ) = IBribe(_bribe).rewardData(_token, ts);
_amounts[i] = rewardsPerEpoch;
}
}
_rewards.tokens = _tokens;
_rewards.amounts = _amounts;
_rewards.symbols = _symbol;
_rewards.decimals = _decimals;
}
struct Claims {
ToCLaim[] toCLaim;
}
struct ToCLaim {
address[] tokens;
string[] symbols;
uint[] decimals;
uint[] amounts;
address bribe;
}
function getAmountToClaim(
address _user,
address[] memory _bribes,
address[][] memory _tokens
) external view returns (ToCLaim[] memory _toClaim) {
require(_user != address(0), "user needs to be != 0");
uint len = _bribes.length;
_toClaim = new ToCLaim[](len);
address _bribe;
uint len2;
address _token;
uint _amount;
for (uint i; i < len; i++) {
_bribe = _bribes[i];
if (_bribe == address(0)) {
continue;
}
len2 = _tokens[i].length;
if (len2 == 0) {
continue;
}
address[] memory _tokensReward = new address[](len2);
uint[] memory _amounts = new uint[](len2);
for (uint u; u < len2; u++) {
_token = _tokens[i][u];
_amount = IBribe(_bribe).earned(_user, _token);
if (_amount != 0) {
_tokensReward[u] = _token;
_amounts[u] = _amount;
}
}
_toClaim[i].bribe = _bribe;
_toClaim[i].tokens = _tokensReward;
_toClaim[i].amounts = _amounts;
}
}
struct BribeAvailableRewards {
address[] tokens;
uint256[] amounts;
address bribe;
}
function getAvailableBribesRewards(
address user_,
uint256 limit_,
uint256 offset_
) external view returns (BribeAvailableRewards[] memory array) {
IExtendVoter voterCache = IExtendVoter(address(voter));
(uint256 totalCount, , ) = voterCache.poolsCounts();
uint256 size = totalCount;
if (offset_ >= size) {
array = new BribeAvailableRewards[](0);
return array;
}
size -= offset_;
if (size > limit_) {
size = limit_;
}
BribeAvailableRewards[] memory fullArray = new BribeAvailableRewards[](size * 2);
uint256 counterBribeWithRewards;
for (uint256 i; i < size; ) {
address pool = voterCache.pools(i + offset_);
address gauge = voterCache.poolToGauge(pool);
IExtendVoter.GaugeState memory state = voterCache.getGaugeState(gauge);
address[] memory rewardTokens = IBribe(state.internalBribe).getRewardTokens();
BribeAvailableRewards memory internalBribeResult = _getBribeRewards(user_, IBribe(state.internalBribe), rewardTokens);
if (internalBribeResult.amounts.length > 0) {
fullArray[counterBribeWithRewards] = internalBribeResult;
counterBribeWithRewards++;
}
BribeAvailableRewards memory externalBribeResult = _getBribeRewards(user_, IBribe(state.externalBribe), rewardTokens);
if (externalBribeResult.amounts.length > 0) {
fullArray[counterBribeWithRewards] = externalBribeResult;
counterBribeWithRewards++;
}
unchecked {
i++;
}
}
array = new BribeAvailableRewards[](counterBribeWithRewards);
for (uint256 i; i < counterBribeWithRewards; ) {
array[i] = fullArray[i];
unchecked {
i++;
}
}
}
function getBribeRewards(address user_, IBribe bribe_) public view returns (BribeAvailableRewards memory result) {
return _getBribeRewards(user_, bribe_, bribe_.getRewardTokens());
}
function _getBribeRewards(
address user_,
IBribe bribe_,
address[] memory tokens_
) internal view returns (BribeAvailableRewards memory result) {
uint256[] memory earnedAmounts = new uint256[](tokens_.length);
uint256 countGtZero;
for (uint256 i; i < tokens_.length; ) {
earnedAmounts[i] = bribe_.earned(user_, tokens_[i]);
if (earnedAmounts[i] > 0) {
countGtZero++;
}
unchecked {
i++;
}
}
result.bribe = address(bribe_);
result.amounts = new uint256[](countGtZero);
result.tokens = new address[](countGtZero);
uint256 j;
for (uint256 i; i < tokens_.length; ) {
if (earnedAmounts[i] > 0) {
result.amounts[j] = earnedAmounts[i];
result.tokens[j] = tokens_[i];
j++;
}
unchecked {
i++;
}
}
}
}
contracts/vesting/interfaces/IMinimalLinearVesting.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
/**
* @title IMinimalLinearVesting
* @dev Interface for the MinimalLinearVestingUpgradeable contract.
*/
interface IMinimalLinearVesting {
/**
* @dev Emitted when a user successfully claims tokens.
* @param caller The address of the user claiming tokens.
* @param amount The amount of tokens claimed.
*/
event Claim(address indexed caller, uint256 indexed amount);
/**
* @dev Emitted when the wallet allocations are updated by the owner.
* @param wallets The list of wallet addresses.
* @param allocation The list of corresponding allocations.
*/
event UpdateWalletsAllocation(address[] wallets, uint256[] allocation);
/**
* @dev Emitted when the vesting parameters are updated.
* @param startTimestamp The new start timestamp of the vesting.
* @param duration The new duration of the vesting period.
*/
event UpdateVestingParams(uint256 startTimestamp, uint256 duration);
/**
* @notice Sets the token allocation for multiple wallets.
* @dev Can only be called by the owner and before the vesting has started.
* Reverts with `NotAvailableDuringClaimPhase` if vesting has started.
* Reverts with `ArrayLengthMismatch` if the lengths of `wallets_` and `amounts_` do not match or if they are empty.
* The total allocated amount is adjusted based on the changes in the wallet allocations.
* If the current balance exceeds the new allocation, the excess tokens are transferred to the owner.
* If the current balance is less than the new allocation, the owner must transfer the difference to the contract.
* @param wallets_ The array of wallet addresses.
* @param amounts_ The array of token amounts allocated to each wallet.
*/
function setWalletsAllocation(address[] calldata wallets_, uint256[] calldata amounts_) external;
/**
* @notice Updates the vesting parameters such as the start timestamp and duration.
* @param startTimestamp_ The new vesting start timestamp.
* @param duration_ The new duration of the vesting in seconds.
*/
function setVestingParams(uint256 startTimestamp_, uint256 duration_) external;
/**
* @notice Allows users to claim their vested tokens.
*/
function claim() external;
/**
* @notice Returns the amount of tokens available for claim for a given wallet.
* @param wallet_ The address of the wallet to check.
* @return The amount of tokens available for claim.
*/
function getAvailableForClaim(address wallet_) external view returns (uint256);
/**
* @notice Returns whether the claim phase has started.
* @dev The claim phase starts when the current timestamp is greater than or equal to the `startTimestamp`.
* @return True if the claim phase has started, false otherwise.
*/
function isClaimPhase() external view returns (bool);
/**
* @notice Returns the token address for the vested token.
* @return The address of the vested token.
*/
function token() external view returns (address);
/**
* @notice Returns the timestamp when the vesting period starts.
* @return The timestamp for the start of vesting.
*/
function startTimestamp() external view returns (uint256);
/**
* @notice Returns the duration of the vesting period in seconds.
* @return The duration of the vesting period.
*/
function duration() external view returns (uint256);
/**
* @notice Returns the token allocation for a specific wallet.
* @param wallet The address of the wallet.
* @return The token allocation for the wallet.
*/
function allocation(address wallet) external view returns (uint256);
/**
* @notice Returns the claimed amount of tokens for a specific wallet.
* @param wallet The address of the wallet.
* @return The claimed token amount for the wallet.
*/
function claimed(address wallet) external view returns (uint256);
}
contracts/dexV2/RouterV2.sol
import {IPairFactory} from "./interfaces/IPairFactory.sol";
/**
*Submitted for verification at FtmScan.com on 2022-02-20
*/
// SPDX-License-Identifier: BUSL-1.1
// ftm.guru's extension of Solidly's periphery (Router)
// https://github.com/andrecronje/solidly/blob/master/contracts/BaseV1-periphery.sol
// BaseV1Router02.sol : Supporting Fee-on-transfer Tokens
// https://github.com/ftm1337/solidly-with-FoT/blob/master/contracts/BaseV1-periphery.sol
pragma solidity =0.8.19;
interface IBaseV1Pair {
function transferFrom(address src, address dst, uint amount) external returns (bool);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function burn(address to) external returns (uint amount0, uint amount1);
function mint(address to) external returns (uint liquidity);
function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast);
function getAmountOut(uint, address) external view returns (uint);
}
interface erc20 {
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint amount) external returns (bool);
function decimals() external view returns (uint8);
function symbol() external view returns (string memory);
function balanceOf(address) external view returns (uint);
function allowance(address, address) external view returns (uint);
function transferFrom(address sender, address recipient, uint amount) external returns (bool);
function approve(address spender, uint value) external returns (bool);
}
library Math {
function min(uint a, uint b) internal pure returns (uint) {
return a < b ? a : b;
}
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, "Math: Sub-underflow");
}
}
interface IWETH {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
}
// Experimental Extension [ftm.guru/solidly/BaseV1Router02]
// contract BaseV1Router02 is BaseV1Router01
// with Support for Fee-on-Transfer Tokens
contract RouterV2 {
using Math for uint;
struct route {
address from;
address to;
bool stable;
}
address public immutable factory;
IWETH public immutable wETH;
uint internal constant MINIMUM_LIQUIDITY = 10 ** 3;
// swap event for the referral system
event Swap(address indexed sender, uint amount0In, address _tokenIn, address indexed to, bool stable);
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, "BaseV1Router: EXPIRED");
_;
}
constructor(address _factory, address _wETH) {
factory = _factory;
wETH = IWETH(_wETH);
}
receive() external payable {
assert(msg.sender == address(wETH)); // only accept ETH via fallback from the WETH contract
}
function sortTokens(address tokenA, address tokenB) public pure returns (address token0, address token1) {
require(tokenA != tokenB, "BaseV1Router: IDENTICAL_ADDRESSES");
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "BaseV1Router: ZERO_ADDRESS");
}
function pairFor(address tokenA, address tokenB, bool stable) public view returns (address pair) {
pair = IPairFactory(factory).getPair(tokenA, tokenB, stable);
}
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
function quoteLiquidity(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, "BaseV1Router: INSUFFICIENT_AMOUNT");
require(reserveA > 0 && reserveB > 0, "BaseV1Router: INSUFFICIENT_LIQUIDITY");
amountB = (amountA * reserveB) / reserveA;
}
// fetches and sorts the reserves for a pair
function getReserves(address tokenA, address tokenB, bool stable) public view returns (uint reserveA, uint reserveB) {
(address token0, ) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1, ) = IBaseV1Pair(pairFor(tokenA, tokenB, stable)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// performs chained getAmountOut calculations on any number of pairs
function getAmountOut(uint amountIn, address tokenIn, address tokenOut) public view returns (uint amount, bool stable) {
address pair = pairFor(tokenIn, tokenOut, true);
uint amountStable;
uint amountVolatile;
if (IPairFactory(factory).isPair(pair)) {
amountStable = IBaseV1Pair(pair).getAmountOut(amountIn, tokenIn);
}
pair = pairFor(tokenIn, tokenOut, false);
if (IPairFactory(factory).isPair(pair)) {
amountVolatile = IBaseV1Pair(pair).getAmountOut(amountIn, tokenIn);
}
return amountStable > amountVolatile ? (amountStable, true) : (amountVolatile, false);
}
// performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(uint amountIn, route[] memory routes) public view returns (uint[] memory amounts) {
require(routes.length >= 1, "BaseV1Router: INVALID_PATH");
amounts = new uint[](routes.length + 1);
amounts[0] = amountIn;
for (uint i = 0; i < routes.length; i++) {
address pair = pairFor(routes[i].from, routes[i].to, routes[i].stable);
if (IPairFactory(factory).isPair(pair)) {
amounts[i + 1] = IBaseV1Pair(pair).getAmountOut(amounts[i], routes[i].from);
}
}
}
function isPair(address pair) public view returns (bool) {
return IPairFactory(factory).isPair(pair);
}
function quoteAddLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired
) public view returns (uint amountA, uint amountB, uint liquidity) {
// create the pair if it doesn't exist yet
address _pair = IPairFactory(factory).getPair(tokenA, tokenB, stable);
(uint reserveA, uint reserveB) = (0, 0);
uint _totalSupply = 0;
if (_pair != address(0)) {
_totalSupply = erc20(_pair).totalSupply();
(reserveA, reserveB) = getReserves(tokenA, tokenB, stable);
}
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
liquidity = Math.sqrt(amountA * amountB) - MINIMUM_LIQUIDITY;
} else {
uint amountBOptimal = quoteLiquidity(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
(amountA, amountB) = (amountADesired, amountBOptimal);
liquidity = Math.min((amountA * _totalSupply) / reserveA, (amountB * _totalSupply) / reserveB);
} else {
uint amountAOptimal = quoteLiquidity(amountBDesired, reserveB, reserveA);
(amountA, amountB) = (amountAOptimal, amountBDesired);
liquidity = Math.min((amountA * _totalSupply) / reserveA, (amountB * _totalSupply) / reserveB);
}
}
}
function quoteRemoveLiquidity(
address tokenA,
address tokenB,
bool stable,
uint liquidity
) public view returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
address _pair = IPairFactory(factory).getPair(tokenA, tokenB, stable);
if (_pair == address(0)) {
return (0, 0);
}
(uint reserveA, uint reserveB) = getReserves(tokenA, tokenB, stable);
uint _totalSupply = erc20(_pair).totalSupply();
amountA = (liquidity * reserveA) / _totalSupply; // using balances ensures pro-rata distribution
amountB = (liquidity * reserveB) / _totalSupply; // using balances ensures pro-rata distribution
}
function _addLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal returns (uint amountA, uint amountB) {
require(amountADesired >= amountAMin);
require(amountBDesired >= amountBMin);
// create the pair if it doesn't exist yet
address _pair = IPairFactory(factory).getPair(tokenA, tokenB, stable);
if (_pair == address(0)) {
_pair = IPairFactory(factory).createPair(tokenA, tokenB, stable);
}
(uint reserveA, uint reserveB) = getReserves(tokenA, tokenB, stable);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = quoteLiquidity(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, "BaseV1Router: INSUFFICIENT_B_AMOUNT");
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = quoteLiquidity(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, "BaseV1Router: INSUFFICIENT_A_AMOUNT");
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, stable, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = pairFor(tokenA, tokenB, stable);
_safeTransferFrom(tokenA, msg.sender, pair, amountA);
_safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IBaseV1Pair(pair).mint(to);
}
function addLiquidityETH(
address token,
bool stable,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(token, address(wETH), stable, amountTokenDesired, msg.value, amountTokenMin, amountETHMin);
address pair = pairFor(token, address(wETH), stable);
_safeTransferFrom(token, msg.sender, pair, amountToken);
wETH.deposit{value: amountETH}();
assert(wETH.transfer(pair, amountETH));
liquidity = IBaseV1Pair(pair).mint(to);
// refund dust ETH, if any
if (msg.value > amountETH) _safeTransferETH(msg.sender, msg.value - amountETH);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
bool stable,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountA, uint amountB) {
address pair = pairFor(tokenA, tokenB, stable);
require(IBaseV1Pair(pair).transferFrom(msg.sender, pair, liquidity)); // send liquidity to pair
(uint amount0, uint amount1) = IBaseV1Pair(pair).burn(to);
(address token0, ) = sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, "BaseV1Router: INSUFFICIENT_A_AMOUNT");
require(amountB >= amountBMin, "BaseV1Router: INSUFFICIENT_B_AMOUNT");
}
function removeLiquidityETH(
address token,
bool stable,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
address(wETH),
stable,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
_safeTransfer(token, to, amountToken);
wETH.withdraw(amountETH);
_safeTransferETH(to, amountETH);
}
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
bool stable,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) public returns (uint amountA, uint amountB) {
address pair = pairFor(tokenA, tokenB, stable);
{
uint value = approveMax ? type(uint).max : liquidity;
_trustlessPermit(pair, msg.sender, address(this), value, deadline, v, r, s);
}
(amountA, amountB) = removeLiquidity(tokenA, tokenB, stable, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETHWithPermit(
address token,
bool stable,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) public returns (uint amountToken, uint amountETH) {
address pair = pairFor(token, address(wETH), stable);
uint value = approveMax ? type(uint).max : liquidity;
_trustlessPermit(pair, msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, stable, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, route[] memory routes, address _to) internal virtual {
for (uint i = 0; i < routes.length; i++) {
(address token0, ) = sortTokens(routes[i].from, routes[i].to);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = routes[i].from == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < routes.length - 1 ? pairFor(routes[i + 1].from, routes[i + 1].to, routes[i + 1].stable) : _to;
IBaseV1Pair(pairFor(routes[i].from, routes[i].to, routes[i].stable)).swap(amount0Out, amount1Out, to, new bytes(0));
emit Swap(msg.sender, amounts[i], routes[i].from, _to, routes[i].stable);
}
}
function swapExactTokensForTokensSimple(
uint amountIn,
uint amountOutMin,
address tokenFrom,
address tokenTo,
bool stable,
address to,
uint deadline
) public ensure(deadline) returns (uint[] memory amounts) {
route[] memory routes = new route[](1);
routes[0].from = tokenFrom;
routes[0].to = tokenTo;
routes[0].stable = stable;
amounts = getAmountsOut(amountIn, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "BaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT");
_safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
_swap(amounts, routes, to);
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
route[] memory routes,
address to,
uint deadline
) public ensure(deadline) returns (uint[] memory amounts) {
amounts = getAmountsOut(amountIn, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "BaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT");
_safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
_swap(amounts, routes, to);
}
function swapExactETHForTokens(
uint amountOutMin,
route[] memory routes,
address to,
uint deadline
) public payable ensure(deadline) returns (uint[] memory amounts) {
require(routes[0].from == address(wETH), "BaseV1Router: INVALID_PATH");
amounts = getAmountsOut(msg.value, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "BaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT");
wETH.deposit{value: amounts[0]}();
assert(wETH.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]));
_swap(amounts, routes, to);
}
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
route[] memory routes,
address to,
uint deadline
) public ensure(deadline) returns (uint[] memory amounts) {
require(routes[routes.length - 1].to == address(wETH), "BaseV1Router: INVALID_PATH");
amounts = getAmountsOut(amountIn, routes);
require(amounts[amounts.length - 1] >= amountOutMin, "BaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT");
_safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
_swap(amounts, routes, address(this));
wETH.withdraw(amounts[amounts.length - 1]);
_safeTransferETH(to, amounts[amounts.length - 1]);
}
function UNSAFE_swapExactTokensForTokens(
uint[] memory amounts,
route[] memory routes,
address to,
uint deadline
) public ensure(deadline) returns (uint[] memory) {
_safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
_swap(amounts, routes, to);
return amounts;
}
function _safeTransferETH(address to, uint value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, "TransferHelper: ETH_TRANSFER_FAILED");
}
function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(erc20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
function _safeTransferFrom(address token, address from, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(erc20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
// Experimental Extension [ETH.guru/solidly/BaseV1Router02]
// **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens)****
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
bool stable,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
address(wETH),
stable,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
_safeTransfer(token, to, erc20(token).balanceOf(address(this)));
wETH.withdraw(amountETH);
_safeTransferETH(to, amountETH);
}
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
bool stable,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) public returns (uint amountToken, uint amountETH) {
address pair = pairFor(token, address(wETH), stable);
uint value = approveMax ? type(uint).max : liquidity;
_trustlessPermit(pair, msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETHSupportingFeeOnTransferTokens(
token,
stable,
liquidity,
amountTokenMin,
amountETHMin,
to,
deadline
);
}
// **** SWAP (supporting fee-on-transfer tokens) ****
// requires the initial amount to have already been sent to the first pair
function _swapSupportingFeeOnTransferTokens(route[] memory routes, address _to) internal virtual {
for (uint i; i < routes.length; i++) {
(address input, address output) = (routes[i].from, routes[i].to);
(address token0, ) = sortTokens(input, output);
IBaseV1Pair pair = IBaseV1Pair(pairFor(routes[i].from, routes[i].to, routes[i].stable));
uint amountInput;
uint amountOutput;
{
// scope to avoid stack too deep errors
(uint reserve0, uint reserve1, ) = pair.getReserves();
(uint reserveInput, ) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = erc20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = pair.getAmountOut(amountInput, input);
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < routes.length - 1 ? pairFor(routes[i + 1].from, routes[i + 1].to, routes[i + 1].stable) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
bool _stable = routes[i].stable;
emit Swap(msg.sender, amountInput, input, _to, _stable);
}
}
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
route[] memory routes,
address to,
uint deadline
) public ensure(deadline) {
_safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amountIn);
uint balanceBefore = erc20(routes[routes.length - 1].to).balanceOf(to);
_swapSupportingFeeOnTransferTokens(routes, to);
require(
erc20(routes[routes.length - 1].to).balanceOf(to).sub(balanceBefore) >= amountOutMin,
"BaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT"
);
}
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
route[] memory routes,
address to,
uint deadline
) public payable ensure(deadline) {
require(routes[0].from == address(wETH), "BaseV1Router: INVALID_PATH");
uint amountIn = msg.value;
wETH.deposit{value: amountIn}();
assert(wETH.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amountIn));
uint balanceBefore = erc20(routes[routes.length - 1].to).balanceOf(to);
_swapSupportingFeeOnTransferTokens(routes, to);
require(
erc20(routes[routes.length - 1].to).balanceOf(to).sub(balanceBefore) >= amountOutMin,
"BaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT"
);
}
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
route[] memory routes,
address to,
uint deadline
) public ensure(deadline) {
require(routes[routes.length - 1].to == address(wETH), "BaseV1Router: INVALID_PATH");
_safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amountIn);
_swapSupportingFeeOnTransferTokens(routes, address(this));
uint amountOut = erc20(address(wETH)).balanceOf(address(this));
require(amountOut >= amountOutMin, "BaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT");
wETH.withdraw(amountOut);
_safeTransferETH(to, amountOut);
}
function _trustlessPermit(
address token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
// Try permit() before allowance check to advance nonce if possible
try IBaseV1Pair(token).permit(owner, spender, value, deadline, v, r, s) {
return;
} catch {
// Permit potentially got frontran. Continue anyways if allowance is sufficient.
if (erc20(token).allowance(owner, spender) >= value) {
return;
}
}
revert("Permit failure");
}
}
@openzeppelin/contracts/token/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
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 amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` 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 amount) external returns (bool);
}
@openzeppelin/contracts/utils/math/SignedMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
contracts/mocks/CompileMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {ERC721PresetMinterPauserAutoId} from "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";
contracts/core/libraries/LibVoterErrors.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
/**
* @notice Reverts when access is denied for the operation.
*/
error AccessDenied();
/**
* @notice Reverts when an invalid address key is provided.
*/
error InvalidAddressKey();
/**
* @notice Reverts when the vote delay has already been set.
*/
error VoteDelayAlreadySet();
/**
* @notice Reverts when the provided vote delay exceeds the maximum allowed.
*/
error VoteDelayTooBig();
/**
* @notice Reverts when an operation is attempted on a gauge that has already been killed.
*/
error GaugeAlreadyKilled();
/**
* @notice Reverts when an operation is attempted on a gauge that is not currently killed.
*/
error GaugeNotKilled();
/**
* @notice Reverts when an operation is attempted on a pool that was not created by the factory.
*/
error PoolNotCreatedByFactory();
/**
* @notice Reverts when an attempt is made to create a gauge for a pool that already has one.
*/
error GaugeForPoolAlreadyExists();
/**
* @notice Reverts when a voting operation is attempted without a prior reset.
*/
error NoResetBefore();
/**
* @notice Reverts when the calculated vote power for a pool is zero.
*/
error ZeroPowerForPool();
/**
* @notice Reverts when the required delay period for voting has not passed.
*/
error VoteDelay();
/**
* @notice Reverts when the lengths of provided arrays do not match.
*/
error ArrayLengthMismatch();
/**
* @notice Reverts when an operation is attempted on a disabled managed NFT.
*/
error DisabledManagedNft();
/**
* @notice Reverts when the operation is attempted outside the allowed distribution window.
*/
error DistributionWindow();
/**
* @notice Reverts when the try create gauge for pool without setup fees vault.
*/
error PoolNotInitialized();
/**
* @notice Reverts if voting is currently paused and an action that requires active voting is attempted.
*/
error DisableDuringVotingPaused();
/**
* @notice Reverts if the percentage to lock (e.g., in a veNFT lock) exceeds the maximum permissible value (1e18 = 100%).
*/
error InvalidPercentageToLock();
contracts/mocks/CoreMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
contract CoreMock {
function isGovernorOrGuardian(address) external view returns (bool) {
return true;
}
}
contracts/mocks/ManagedNFTManagerMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "../core/interfaces/IVotingEscrow.sol";
contract ManagedNFTManagerMock {
mapping(uint256 => bool) public isManagedNFT;
function setIsManagedNft(uint256 tokenId) external {
isManagedNFT[tokenId] = true;
}
function getAttachedManagedTokenId(uint256) external view returns (uint256) {
return 0;
}
function onAttachToManagedNFT(address votingEscrow, uint256 tokenId_, uint256 managedTokenId_) external {
IVotingEscrow(votingEscrow).onAttachToManagedNFT(tokenId_, managedTokenId_);
}
function onDettachFromManagedNFT(address votingEscrow, uint256 tokenId_, uint256 managedTokenId_, uint256 newBalance_) external {
IVotingEscrow(votingEscrow).onDettachFromManagedNFT(tokenId_, managedTokenId_, newBalance_);
}
function create(address votingEscrow, address recipient) external {
ManagedNFTManagerMock(votingEscrow).createManagedNFT(recipient);
}
function createManagedNFT(address recipient_) external {}
}
contracts/core/LuteRaiseUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import {MerkleProofUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
import {ILuteRaise} from "./interfaces/ILuteRaise.sol";
import {IVotingEscrow} from "../core/interfaces/IVotingEscrow.sol";
/**
* @title LuteRaiseUpgradeable
* @dev This contract manages a token raise with both whitelist and public phases.
* It utilizes Merkle proof verification for whitelist management and ensures various caps
* and limits are adhered to during the raise.
*/
contract LuteRaiseUpgradeable is ILuteRaise, Ownable2StepUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @dev The duration for which NFT tokens will be locked.
*/
uint256 internal constant _LOCK_DURATION = 182 days;
/**
* @dev Precision used for percentage calculations to ensure accurate arithmetic operations.
*/
uint256 internal constant _PRECISION = 1e18;
/**
* @notice The address of the reward token.
* @dev This is the token that users will receive as a reward for their deposits.
*/
address public override rewardToken;
/**
* @notice The address of the token being raised
*/
address public override token;
/**
* @notice The address of the voting escrow contract.
* @dev This contract manages the locking of reward tokens into veNFTs.
*/
address public override votingEscrow;
/**
* @notice The address that will receive the deposits
*/
address public override depositsReciever;
/**
* @notice The Merkle root for the whitelist verification
*/
bytes32 public override whitelistMerklRoot;
/**
* @notice The timestamp for the start of the whitelist phase
*/
uint256 public override startWhitelistPhaseTimestamp;
/**
* @notice The timestamp for the start of the public phase
*/
uint256 public override startPublicPhaseTimestamp;
/**
* @notice The timestamp for the end of the public phase
*/
uint256 public override endPublicPhaseTimestamp;
/**
* @notice The maximum amount a user can deposit during the whitelist phase
*/
uint256 public override whitelistPhaseUserCap;
/**
* @notice The timestamp for the start of the claim phase
*/
uint256 public override startClaimPhaseTimestamp;
/**
* @notice The maximum amount a user can deposit during the public phase
*/
uint256 public override publicPhaseUserCap;
/**
* @notice The amount of reward tokens per deposit token.
* @dev Specifies the conversion rate between deposit tokens and reward tokens.
*/
uint256 public override amountOfRewardTokenPerDepositToken;
/**
* @dev Percentage of the claimed amount to be locked as veNFT.
* This value should be set as a fraction of 1e18 (e.g., 0.5 * 1e18 represents 50%).
*/
uint256 public override toVeNftPercentage;
/**
* @notice The total cap for deposits
*/
uint256 public override totalDepositCap;
/**
* @notice The total amount deposited so far
*/
uint256 public override totalDeposited;
/**
* @notice The total amount reward claimed so far
*/
uint256 public override totalClaimed;
/**
* @notice The amount each user has deposited
* @dev Mapping from user address to the amount deposited
*/
mapping(address => uint256) public override userDeposited;
/**
* @notice The amount each user has deposited during whitelist phase
* @dev Mapping from user address to the amount deposited
*/
mapping(address => uint256) public override userDepositsWhitelistPhase;
/**
* @notice Mapping to track whether a user has claimed their tokens.
* @dev Maps user address to a boolean indicating claim status.
* True if the user has claimed, false otherwise.
*/
mapping(address => bool) public override isUserClaimed;
/**
* @dev Error thrown when the `toVeNftPercentage` is incorrect (i.e., greater than 1e18).
*/
error IncorrectToVeNftPercentage();
/**
* @dev Error thrown when timestamps are incorrect
*/
error IncorrectTimestamps();
/**
* @dev Error thrown when a non-whitelisted user attempts to deposit during the whitelist phase
*/
error OnlyForWhitelistedUser();
/**
* @dev Error thrown when claim phase not started at the moment
*/
error ClaimPhaseNotStarted();
/**
* @dev Error thrown when deposits are closed
*/
error DepositClosed();
/**
* @dev Error thrown when a user attempts to deposit more than the user cap
*/
error UserDepositCap();
/**
* @dev Error thrown when the total deposit cap is exceeded
*/
error TotalDepositCap();
/**
* @dev Error thrown when trying to withdraw deposits before the raise is finished
*/
error RaiseNotFinished();
/**
* @dev Error thrown when a zero amount is involved in a transaction
*/
error ZeroAmount();
/**
* @dev Error thrown when a user tries to claim more than once
*/
error AlreadyClaimed();
error AddressZero();
/**
* @dev Initializes the contract by disabling the initializer of the inherited upgradeable contract.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract
* @param token_ The address of the token being raised
* @param rewardToken_ The address of the reward token
* @param depositsReciever_ The address that will receive the deposits
* @param amountOfRewardTokenPerDepositToken_ The amount of reward tokens per deposit token
* @param votingEscrow_ The address of the voting escrow
* @param toVeNftPercentage_ The percentage of the claimed amount to be locked as veNFT
*/
function initialize(
address token_,
address rewardToken_,
address depositsReciever_,
uint256 amountOfRewardTokenPerDepositToken_,
address votingEscrow_,
uint256 toVeNftPercentage_
) external initializer {
_checkAddressZero(token_);
_checkAddressZero(rewardToken_);
_checkAddressZero(depositsReciever_);
if (toVeNftPercentage_ > 0) {
if (toVeNftPercentage_ > _PRECISION) {
revert IncorrectToVeNftPercentage();
}
_checkAddressZero(votingEscrow_);
}
__Ownable2Step_init();
rewardToken = rewardToken_;
token = token_;
depositsReciever = depositsReciever_;
amountOfRewardTokenPerDepositToken = amountOfRewardTokenPerDepositToken_;
votingEscrow = votingEscrow_;
toVeNftPercentage = toVeNftPercentage_;
}
/**
* @notice Deposit tokens during the raise
* @param amount_ The amount of tokens to deposit
* @param userCap_ The cap for the user (used for whitelist verification)
* @param proof_ The Merkle proof for verifying the user is whitelisted
*/
function deposit(uint256 amount_, uint256 userCap_, bytes32[] memory proof_) external virtual override {
_checkAmountZero(amount_);
bool isWhitelistPhaseCache = isWhitelistPhase();
uint256 phaseCap;
if (!isPublicPhase()) {
if (isWhitelistPhaseCache) {
if (!isWhitelisted(_msgSender(), userCap_, proof_)) {
revert OnlyForWhitelistedUser();
}
phaseCap = userCap_ > 0 ? userCap_ : whitelistPhaseUserCap;
} else {
revert DepositClosed();
}
} else {
phaseCap = publicPhaseUserCap;
}
uint256 userDepositedCache = isWhitelistPhaseCache
? userDeposited[_msgSender()]
: userDeposited[_msgSender()] - userDepositsWhitelistPhase[_msgSender()];
if (userDepositedCache + amount_ > phaseCap) {
revert UserDepositCap();
}
if (totalDeposited + amount_ > totalDepositCap) {
revert TotalDepositCap();
}
IERC20Upgradeable(token).safeTransferFrom(_msgSender(), address(this), amount_);
if (isWhitelistPhaseCache) {
userDepositsWhitelistPhase[_msgSender()] += amount_;
}
userDeposited[_msgSender()] += amount_;
totalDeposited += amount_;
emit Deposit(_msgSender(), amount_);
}
/**
* @notice Claim tokens after the raise
* @dev Users can claim their reward tokens and veNFTs based on their deposited amount.
* If the user has already claimed, it reverts with `AlreadyClaimed`.
* If the deposited amount is zero, it reverts with `ZeroAmount`.
* If the claim phase not started, it reverts with `ClaimPhaseNotStarted`.
*/
function claim() external virtual override {
if (!isClaimPhase()) {
revert ClaimPhaseNotStarted();
}
if (isUserClaimed[_msgSender()]) {
revert AlreadyClaimed();
}
uint256 depositAmount = userDeposited[_msgSender()];
_checkAmountZero(depositAmount);
(uint256 toRewardTokenAmount, uint256 toVeNftAmount) = getRewardsAmountOut(depositAmount);
totalClaimed += toVeNftAmount + toRewardTokenAmount;
isUserClaimed[_msgSender()] = true;
uint256 tokenId;
IERC20Upgradeable rewardTokenCache = IERC20Upgradeable(rewardToken);
if (toVeNftAmount > 0) {
IVotingEscrow veCache = IVotingEscrow(votingEscrow);
rewardTokenCache.forceApprove(address(veCache), toVeNftAmount);
tokenId = veCache.createLockFor(toVeNftAmount, _LOCK_DURATION, _msgSender(), false, false, 0);
}
if (toRewardTokenAmount > 0) {
rewardTokenCache.safeTransfer(_msgSender(), toRewardTokenAmount);
}
emit Claim(_msgSender(), toVeNftAmount + toRewardTokenAmount, toRewardTokenAmount, toVeNftAmount, tokenId);
}
/**
* @notice Withdraws the deposits after the raise is finished
*/
function whithdrawDeposits() external virtual override onlyOwner {
if (block.timestamp <= endPublicPhaseTimestamp || endPublicPhaseTimestamp == 0) {
revert RaiseNotFinished();
}
IERC20Upgradeable tokenCache = IERC20Upgradeable(token);
uint256 balanace = tokenCache.balanceOf(address(this));
_checkAmountZero(balanace);
tokenCache.safeTransfer(depositsReciever, tokenCache.balanceOf(address(this)));
emit WithdrawDeposits(_msgSender(), depositsReciever, balanace);
}
/**
* @notice Withdraws the unclaimed rewards after the raise is finished
*/
function withdrawExcessiveRewardTokens() external virtual override onlyOwner {
if (block.timestamp <= endPublicPhaseTimestamp || endPublicPhaseTimestamp == 0) {
revert RaiseNotFinished();
}
IERC20Upgradeable rewardTokenCache = IERC20Upgradeable(rewardToken);
(uint256 toRewardTokenAmount, uint256 toVeNftAmount) = getRewardsAmountOut(IERC20Upgradeable(token).balanceOf(address(this)));
uint256 balanace = rewardTokenCache.balanceOf(address(this));
uint256 unclaimedRewards = balanace - (toRewardTokenAmount + toVeNftAmount - totalClaimed);
_checkAmountZero(unclaimedRewards);
rewardTokenCache.safeTransfer(depositsReciever, unclaimedRewards);
emit WithdrawExcessiveRewardTokens(_msgSender(), depositsReciever, unclaimedRewards);
}
/**
* @notice Sets the deposit caps
* @param totalDepositCap_ The total deposit cap
* @param whitelistPhaseUserCap_ The user cap for the whitelist phase
* @param publicPhaseUserCap_ The user cap for the public phase
*/
function setDepositCaps(
uint256 totalDepositCap_,
uint256 whitelistPhaseUserCap_,
uint256 publicPhaseUserCap_
) external virtual override onlyOwner {
totalDepositCap = totalDepositCap_;
whitelistPhaseUserCap = whitelistPhaseUserCap_;
publicPhaseUserCap = publicPhaseUserCap_;
emit UpdateDepositCaps(totalDepositCap_, whitelistPhaseUserCap_, publicPhaseUserCap_);
}
/**
* @notice Sets the whitelist root
* @param root_ The new whitelist root
*/
function setWhitelistRoot(bytes32 root_) external virtual override onlyOwner {
whitelistMerklRoot = root_;
emit UpdateWhitelistRoot(root_);
}
/**
* @notice Sets the timestamps for the phases
* @param startWhitelistPhaseTimestamp_ The timestamp for the start of the whitelist phase
* @param startPublicPhaseTimestamp_ The timestamp for the start of the public phase
* @param endPublicPhaseTimestamp_ The timestamp for the end of the public phase
* @param startClaimPhaseTimestamp_ The timestamp for the start of the claim phase
*/
function setTimestamps(
uint256 startWhitelistPhaseTimestamp_,
uint256 startPublicPhaseTimestamp_,
uint256 endPublicPhaseTimestamp_,
uint256 startClaimPhaseTimestamp_
) external virtual override onlyOwner {
if (
startWhitelistPhaseTimestamp_ >= startPublicPhaseTimestamp_ ||
startWhitelistPhaseTimestamp_ >= endPublicPhaseTimestamp_ ||
startPublicPhaseTimestamp_ >= endPublicPhaseTimestamp_ ||
endPublicPhaseTimestamp_ >= startClaimPhaseTimestamp_
) {
revert IncorrectTimestamps();
}
startWhitelistPhaseTimestamp = startWhitelistPhaseTimestamp_;
startPublicPhaseTimestamp = startPublicPhaseTimestamp_;
endPublicPhaseTimestamp = endPublicPhaseTimestamp_;
startClaimPhaseTimestamp = startClaimPhaseTimestamp_;
emit UpdateTimestamps(
startWhitelistPhaseTimestamp_,
startPublicPhaseTimestamp_,
endPublicPhaseTimestamp_,
startClaimPhaseTimestamp_
);
}
/**
* @notice Checks if a user is whitelisted
* @param user_ The address of the user
* @param userCap_ The cap for the user
* @param proof_ The Merkle proof for verifying the user
* @return True if the user is whitelisted, false otherwise
*/
function isWhitelisted(address user_, uint256 userCap_, bytes32[] memory proof_) public view virtual override returns (bool) {
bytes32 root = whitelistMerklRoot;
if (proof_.length == 0 || root == bytes32(0)) {
return false;
}
return MerkleProofUpgradeable.verify(proof_, root, keccak256(bytes.concat(keccak256(abi.encode(user_, userCap_)))));
}
/**
* @notice Checks if the whitelist phase is active
* @return True if the whitelist phase is active, false otherwise
*/
function isWhitelistPhase() public view virtual override returns (bool) {
return (block.timestamp >= startWhitelistPhaseTimestamp && block.timestamp < startPublicPhaseTimestamp);
}
/**
* @notice Checks if the public phase is active
* @return True if the public phase is active, false otherwise
*/
function isPublicPhase() public view virtual override returns (bool) {
return (block.timestamp >= startPublicPhaseTimestamp && block.timestamp <= endPublicPhaseTimestamp);
}
/**
* @notice Checks if the claim phase is active
* @return True if the claim phase is active, false otherwise
*/
function isClaimPhase() public view virtual override returns (bool) {
uint256 timestamp = startClaimPhaseTimestamp;
return (block.timestamp > timestamp && timestamp != 0);
}
/**
* @notice Gets the reward amounts out based on the deposit amount
* @param depositAmount_ The amount of tokens deposited
* @return toRewardTokenAmount The amount of reward tokens
* @return toVeNftAmount The amount to veNFT
*/
function getRewardsAmountOut(
uint256 depositAmount_
) public view virtual override returns (uint256 toRewardTokenAmount, uint256 toVeNftAmount) {
uint256 totalAmount = (depositAmount_ * amountOfRewardTokenPerDepositToken) / (10 ** IERC20MetadataUpgradeable(token).decimals());
toVeNftAmount = (totalAmount * toVeNftPercentage) / _PRECISION;
toRewardTokenAmount = totalAmount - toVeNftAmount;
}
/**
* @dev Checks if the amount is zero
* @param amount_ The amount to check
* @notice Reverts with `ZeroAmount` if the amount is zero
*/
function _checkAmountZero(uint256 amount_) internal pure virtual {
if (amount_ == 0) {
revert ZeroAmount();
}
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert AddressZero();
}
}
}
contracts/lute/StrategyProxy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import {ICompoundVeLUTEManagedNFTStrategyFactory} from "./interfaces/ICompoundVeLUTEManagedNFTStrategyFactory.sol";
contract StrategyProxy {
address private immutable factory;
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
constructor() {
factory = msg.sender;
}
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
function _setImplementation(address newImplementation) private {
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
fallback() external payable {
address impl = ICompoundVeLUTEManagedNFTStrategyFactory(factory).strategyImplementation();
require(impl != address(0));
//Just for etherscan compatibility
if (impl != _getImplementation() && msg.sender != (address(0))) {
_setImplementation(impl);
}
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
}
@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol)
pragma solidity ^0.8.0;
import "../ERC721Upgradeable.sol";
import "./IERC721EnumerableUpgradeable.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";
/**
* @dev This implements an optional extension of {ERC721} defined in the EIP that adds
* enumerability of all the token ids in the contract as well as all token ids owned by each
* account.
*/
abstract contract ERC721EnumerableUpgradeable is Initializable, ERC721Upgradeable, IERC721EnumerableUpgradeable {
// Mapping from owner to list of owned token IDs
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
// Mapping from token ID to index of the owner tokens list
mapping(uint256 => uint256) private _ownedTokensIndex;
// Array with all token ids, used for enumeration
uint256[] private _allTokens;
// Mapping from token id to position in the allTokens array
mapping(uint256 => uint256) private _allTokensIndex;
function __ERC721Enumerable_init() internal onlyInitializing {
}
function __ERC721Enumerable_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165Upgradeable, ERC721Upgradeable) returns (bool) {
return interfaceId == type(IERC721EnumerableUpgradeable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
require(index < ERC721Upgradeable.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
/**
* @dev See {IERC721Enumerable-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _allTokens.length;
}
/**
* @dev See {IERC721Enumerable-tokenByIndex}.
*/
function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
require(index < ERC721EnumerableUpgradeable.totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
/**
* @dev See {ERC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
if (batchSize > 1) {
// Will only trigger during construction. Batch transferring (minting) is not available afterwards.
revert("ERC721Enumerable: consecutive transfers not supported");
}
uint256 tokenId = firstTokenId;
if (from == address(0)) {
_addTokenToAllTokensEnumeration(tokenId);
} else if (from != to) {
_removeTokenFromOwnerEnumeration(from, tokenId);
}
if (to == address(0)) {
_removeTokenFromAllTokensEnumeration(tokenId);
} else if (to != from) {
_addTokenToOwnerEnumeration(to, tokenId);
}
}
/**
* @dev Private function to add a token to this extension's ownership-tracking data structures.
* @param to address representing the new owner of the given token ID
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
*/
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = ERC721Upgradeable.balanceOf(to);
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;
}
/**
* @dev Private function to add a token to this extension's token tracking data structures.
* @param tokenId uint256 ID of the token to be added to the tokens list
*/
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
/**
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
* @param from address representing the previous owner of the given token ID
* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
*/
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = ERC721Upgradeable.balanceOf(from) - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
}
// This also deletes the contents at the last position of the array
delete _ownedTokensIndex[tokenId];
delete _ownedTokens[from][lastTokenIndex];
}
/**
* @dev Private function to remove a token from this extension's token tracking data structures.
* This has O(1) time complexity, but alters the order of the _allTokens array.
* @param tokenId uint256 ID of the token to be removed from the tokens list
*/
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
// This also deletes the contents at the last position of the array
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[46] private __gap;
}
contracts/integration/interfaces/IOpenOceanVeNftDirectBuyer.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "./IOpenOceanExchange.sol";
import "./IOpenOceanCaller.sol";
/**
* @title IOpenOceanVeNftDirectBuyer
* @notice Interface for a contract facilitating direct veNFT purchases via OpenOcean swaps.
*/
interface IOpenOceanVeNftDirectBuyer {
/**
* @notice Parameters for creating a veNFT through VotingEscrow.
* @param lockDuration The duration for which the tokens will be locked.
* @param to The address that will receive the veNFT.
* @param shouldBoosted Indicates whether the veNFT should have boosted properties.
* @param withPermanentLock Indicates if the lock should be permanent.
* @param managedTokenIdForAttach The ID of the managed veNFT token to which this will be attached.
*/
struct VotingEscrowCreateLockForParams {
uint256 lockDuration;
address to;
bool shouldBoosted;
bool withPermanentLock;
uint256 managedTokenIdForAttach;
}
/**
* @notice Emitted after a successful direct veNFT purchase.
* @param caller The address of the function caller.
* @param recipient The address of the veNFT recipient.
* @param srcToken The address of the source token used for the swap.
* @param spentAmount The amount of source tokens spent in the swap.
* @param tokenAmount The amount of destination tokens obtained in the swap.
* @param veNftTokenId The ID of the veNFT created for the recipient.
*/
event DirectVeNftPurchase(
address indexed caller,
address indexed recipient,
address indexed srcToken,
uint256 spentAmount,
uint256 tokenAmount,
uint256 veNftTokenId
);
/**
* @notice Facilitates a direct purchase of veNFTs by performing a token swap and veNFT creation.
* @dev The function validates inputs, executes the swap, and creates a veNFT for the recipient.
* @param caller_ The OpenOcean caller contract.
* @param desc_ The swap description containing details of the source and destination tokens.
* @param calls_ The calls to execute as part of the OpenOcean swap.
* @param votingEscrowCreateForParams_ Parameters for creating the veNFT.
* @return tokenAmount The amount of destination tokens obtained in the swap.
* @return tokenId The ID of the veNFT created.
* @custom:requirements The destination token must match the expected token, and the caller must provide sufficient balance.
* @custom:emits Emits a `DirectVeNftPurchase` event on successful veNFT creation.
*/
function directVeNftPurchase(
IOpenOceanCaller caller_,
IOpenOceanExchange.SwapDescription calldata desc_,
IOpenOceanCaller.CallDescription[] calldata calls_,
VotingEscrowCreateLockForParams calldata votingEscrowCreateForParams_
) external payable returns (uint256 tokenAmount, uint256 tokenId);
}
contracts/core/interfaces/IVeArtProxy.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title IVeArtProxy Interface
* @author Lute Protocol team
* @dev Interface for the VeArtProxyUpgradeable contract.
* This interface outlines the tokenURI function which is responsible
* for generating on-chain art in the form of SVG images, encoded in base64 format,
* providing a unique visual representation for each token.
*/
interface IVeArtProxy {
/**
* @dev Generates a {ERC721.tokenURI} with on-chain generated art.
*
* This function generates an SVG image representing the state of the token.
* The SVG includes visual representations of the token's current voting power,
* the timestamp of when the token's lock ends, and the amount of tokens locked.
* This SVG image is encoded in base64 and included in a JSON metadata structure,
* which is also encoded in base64. This metadata provides a unique, on-chain
* representation for each token, enhancing its traceability and uniqueness.
*
* @param tokenId_ The ID of the token. Used to uniquely identify the token for which the URI is being generated.
* @return output The base64 encoded JSON metadata for the token
*/
function tokenURI(uint256 tokenId_) external view returns (string memory output);
}
contracts/bribes/rewards/interfaces/ICustomBribeRewardRouter.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title ICustomBribeRewardRouter
* @notice This interface defines the functions and events for a custom bribe reward router.
* It handles the conversion of LUTE or veLUTE NFTs into intermediary brVeLUTE tokens,
* and then notifies external bribe contracts of these newly available rewards.
*/
interface ICustomBribeRewardRouter {
/**
* @dev Emitted when the contract configuration is changed to enable or disable a specific function.
* @param funcSign The 4-byte function selector indicating the targeted function.
* @param isEnable A boolean indicating whether the function is now enabled (true) or disabled (false).
*/
event FuncEnabled(bytes4 indexed funcSign, bool isEnable);
/**
* @dev Emitted when LUTE tokens are converted into brVeLUTE and distributed to a specified external bribe contract.
* @param caller The address that initiated the reward notification.
* @param pool The address of the pool for which the reward is being notified.
* @param externalBribe The address of the external bribe contract receiving the reward.
* @param amount The amount of LUTE (converted into brVeLUTE) notified as a reward.
*/
event NotifyRewardLUTEInVeLute(address indexed caller, address indexed pool, address indexed externalBribe, uint256 amount);
/**
* @dev Emitted when a veLUTE NFT is burned and its underlying LUTE is converted into brVeLUTE,
* then notified to a specified external bribe contract.
* @param caller The address that initiated the reward notification.
* @param pool The address of the pool for which the reward is being notified.
* @param externalBribe The address of the external bribe contract receiving the reward.
* @param tokenId The identifier of the veLUTE NFT that was burned.
* @param amount The amount of LUTE (converted into brVeLUTE) notified as a reward.
*/
event NotifyRewardVeLUTEInVeLute(
address indexed caller,
address indexed pool,
address externalBribe,
uint256 indexed tokenId,
uint256 amount
);
/**
* @notice Notifies an external bribe contract that LUTE has been converted into brVeLUTE and is ready as a reward.
* @dev LUTE tokens are first transferred into
* this contract, converted into brVeLUTE, and then notified to the external bribe contract linked to the given pool.
* @param pool_ The address of the pool for which the reward is being notified.
* @param amount_ The amount of LUTE to be converted into brVeLUTE and notified as a reward.
*
* Emits a {NotifyRewardLUTEInVeLute} event.
* Reverts if:
* - The functionality is disabled.
* - The corresponding external bribe contract cannot be found or is invalid.
*/
function notifyRewardLUTEInVeLUTE(address pool_, uint256 amount_) external;
/**
* @notice Notifies an external bribe contract using LUTE converted from a burned veLUTE NFT.
* @dev The veLUTE NFT is transferred
* into this contract, burned to reclaim the underlying LUTE, converted into brVeLUTE, and then
* notified to the external bribe contract linked to the given pool.
* @param pool_ The address of the pool for which the reward is being notified.
* @param tokenId_ The ID of the veLUTE NFT to be burned and converted into brVeLUTE as a reward.
*
* Emits a {NotifyRewardVeLUTEInVeLute} event.
* Reverts if:
* - The functionality is disabled.
* - The corresponding external bribe contract cannot be found or is invalid.
* - The token state does not allow for burning (e.g., permanently locked, attached, or recently voted).
*/
function notifyRewardVeLUTEInVeLute(address pool_, uint256 tokenId_) external;
}
contracts/core/VeLuteSplitMerklAidropUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
import {IVeLuteSplitMerklAidrop} from "./interfaces/IVeLuteSplitMerklAidrop.sol";
import {MerkleProofUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
/**
* @title VeLuteSplitMerklAidropUpgradeable
* @dev A contract for handling token and veNft token claims based on a Merkle tree proof.
*/
contract VeLuteSplitMerklAidropUpgradeable is IVeLuteSplitMerklAidrop, Ownable2StepUpgradeable, PausableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @dev The duration for which veNFT tokens will be locked.
*/
uint256 internal constant _LOCK_DURATION = 182 days;
/**
* @dev Precision used for percentage calculations to ensure accurate arithmetic operations.
*/
uint256 internal constant _PRECISION = 1e18;
/**
* @dev Address of the token contract.
*/
address public override token;
/**
* @dev Address of the Voting Escrow contract used for veNFT tokens.
*/
address public override votingEscrow;
/**
* @dev Rate for pure tokens.
*/
uint256 public override pureTokensRate;
/**
* @dev Merkle root used for verifying user claims.
*/
bytes32 public override merklRoot;
/**
* @dev Mapping of user addresses to the amount of tokens they have claimed.
*/
mapping(address => uint256) public override userClaimed;
/**
* @dev Mapping to check if an address is an allowed claim operator.
*/
mapping(address => bool) public override isAllowedClaimOperator;
/**
* @dev Error thrown when the pure tokens rate is incorrect.
*/
error IncorrectPureTokensRate();
/**
* @dev Error thrown when a provided Merkle proof is invalid.
*/
error InvalidProof();
/**
* @dev Error thrown when the claim amount is zero.
*/
error ZeroAmount();
/**
* @dev Error thrown when a caller is not an allowed claim operator.
*/
error NotAllowedClaimOperator();
/**
* @dev Error thrown when the pure tokens rate is zero.
*/
error ZeroPureTokensRate();
error AddressZero();
/**
* @dev Initializes the contract by disabling the initializer of the inherited upgradeable contract.
*/
constructor() {
_disableInitializers();
}
/**
* @dev Initializes the contract with the provided parameters.
* @param token_ Address of the token contract.
* @param votingEscrow_ Address of the Voting Escrow contract.
* @param pureTokensRate_ Rate for pure tokens.
* @notice This function can only be called once.
*/
function initialize(address token_, address votingEscrow_, uint256 pureTokensRate_) external virtual initializer {
_checkAddressZero(token_);
_checkAddressZero(votingEscrow_);
_checkPureTokensRate(pureTokensRate_);
__Ownable2Step_init();
__Pausable_init();
_pause();
token = token_;
votingEscrow = votingEscrow_;
pureTokensRate = pureTokensRate_;
}
/**
* @dev Allows a user to claim tokens or veNFT tokens based on a Merkle proof.
* @param inPureTokens_ Boolean indicating if the claim is in pure tokens.
* @param amount_ The amount to claim.
* @param withPermanentLock_ Whether the lock should be permanent.
* @param managedTokenIdForAttach_ The ID of the managed NFT to attach, if any. 0 for ignore
* @param proof_ The Merkle proof for the claim.
* @notice This function can only be called when the contract is not paused.
*/
function claim(
bool inPureTokens_,
uint256 amount_,
bool withPermanentLock_,
uint256 managedTokenIdForAttach_,
bytes32[] memory proof_
) external virtual override whenNotPaused {
_claim(_msgSender(), inPureTokens_, amount_, withPermanentLock_, managedTokenIdForAttach_, proof_);
}
/**
* @dev Allows a claim operator to claim tokens on behalf of a target address.
* @param target_ The address of the user on whose behalf tokens are being claimed.
* @param inPureTokens_ Boolean indicating if the claim is in pure tokens.
* @param amount_ The amount to claim.
* @param withPermanentLock_ Whether the lock should be permanent.
* @param managedTokenIdForAttach_ The ID of the managed NFT to attach, if any. 0 for ignore
* @param proof_ The Merkle proof verifying the user's claim.
* @notice This function can only be called when the contract is not paused.
* @notice Reverts with `NotAllowedClaimOperator` if the caller is not an allowed claim operator.
* @notice Emits a {Claim} event.
*/
function claimFor(
address target_,
bool inPureTokens_,
uint256 amount_,
bool withPermanentLock_,
uint256 managedTokenIdForAttach_,
bytes32[] memory proof_
) external virtual override whenNotPaused {
if (target_ != _msgSender() && !isAllowedClaimOperator[_msgSender()]) {
revert NotAllowedClaimOperator();
}
_claim(target_, inPureTokens_, amount_, withPermanentLock_, managedTokenIdForAttach_, proof_);
}
/**
* @dev Pauses the contract, preventing any further claims.
* Can only be called by the owner.
* @notice Emits a {Paused} event.
*/
function pause() external virtual override onlyOwner {
_pause();
}
/**
* @dev Unpauses the contract, allowing claims to be made.
* Can only be called by the owner.
* @notice Emits an {Unpaused} event.
*/
function unpause() external virtual override onlyOwner {
_unpause();
}
/**
* @dev Sets whether an address is allowed to operate claims on behalf of others.
* Can only be called by the owner.
* @param operator_ The address of the operator to set.
* @param isAllowed_ A boolean indicating whether the operator is allowed.
* @notice Emits a {SetIsAllowedClaimOperator} event.
*/
function setIsAllowedClaimOperator(address operator_, bool isAllowed_) external virtual override onlyOwner {
isAllowedClaimOperator[operator_] = isAllowed_;
emit SetIsAllowedClaimOperator(operator_, isAllowed_);
}
/**
* @dev Sets the Merkle root for verifying claims.
* Can only be called by the owner when the contract is paused.
* @param merklRoot_ The new Merkle root.
* @notice Emits a {SetMerklRoot} event.
*/
function setMerklRoot(bytes32 merklRoot_) external virtual override onlyOwner whenPaused {
merklRoot = merklRoot_;
emit SetMerklRoot(merklRoot_);
}
/**
* @dev Sets the pure tokens rate.
* Can only be called by the owner when the contract is paused.
* @param pureTokensRate_ The new pure tokens rate.
* @notice Emits a {SetPureTokensRate} event.
*/
function setPureTokensRate(uint256 pureTokensRate_) external virtual override onlyOwner whenPaused {
_checkPureTokensRate(pureTokensRate_);
pureTokensRate = pureTokensRate_;
emit SetPureTokensRate(pureTokensRate_);
}
/**
* @notice Allows the owner to recover token from the contract.
* @param amount_ The amount of tokens to be recovered.
* Transfers the specified amount of tokens to the owner's address.
*/
function recoverToken(uint256 amount_) external virtual override onlyOwner whenPaused {
IERC20Upgradeable(token).safeTransfer(_msgSender(), amount_);
emit Recover(_msgSender(), amount_);
}
/**
* @dev Verifies if a provided proof is valid for a given user and amount.
* @param user_ The address of the user.
* @param amount_ The amount to be verified.
* @param proof_ The Merkle proof.
* @return True if the proof is valid, false otherwise.
*/
function isValidProof(address user_, uint256 amount_, bytes32[] memory proof_) public view virtual override returns (bool) {
bytes32 root = merklRoot;
if (proof_.length == 0 || root == bytes32(0)) {
return false;
}
return MerkleProofUpgradeable.verify(proof_, root, keccak256(bytes.concat(keccak256(abi.encode(user_, amount_)))));
}
/**
* @dev Calculates the equivalent amount in pure tokens based on the claim amount.
* @param claimAmount_ The claim amount for which to calculate the equivalent pure tokens.
* @return The calculated amount of pure tokens.
*/
function calculatePureTokensAmount(uint256 claimAmount_) public view returns (uint256) {
return (pureTokensRate * claimAmount_) / _PRECISION;
}
/**
* @dev Internal function to handle the claiming process.
* @param target_ The address of the user making the claim.
* @param inPureTokens_ Boolean indicating if the claim is in pure tokens.
* @param amount_ The total amount of tokens the user can claim.
* @param withPermanentLock_ Whether the lock should be permanent.
* @param managedTokenIdForAttach_ The ID of the managed NFT to attach, if any. 0 for ignore
* @param proof_ The Merkle proof verifying the user's claim.
* @notice Reverts with `InvalidProof` if the provided proof is not valid.
* @notice Reverts with `ZeroAmount` if the claim amount is zero.
* @notice Emits a {Claim} event.
*/
function _claim(
address target_,
bool inPureTokens_,
uint256 amount_,
bool withPermanentLock_,
uint256 managedTokenIdForAttach_,
bytes32[] memory proof_
) internal virtual {
if (!isValidProof(target_, amount_, proof_)) {
revert InvalidProof();
}
uint256 claimAmount = amount_ - userClaimed[target_];
if (claimAmount == 0) {
revert ZeroAmount();
}
userClaimed[target_] = amount_;
IERC20Upgradeable tokenCache = IERC20Upgradeable(token);
uint256 tokenId;
uint256 toTokenAmount;
uint256 toVeNFTAmount;
if (inPureTokens_) {
uint256 pureTokensRateCache = pureTokensRate;
if (pureTokensRateCache == 0) {
revert ZeroPureTokensRate();
}
toTokenAmount = calculatePureTokensAmount(claimAmount);
tokenCache.safeTransfer(target_, toTokenAmount);
} else {
toVeNFTAmount = claimAmount;
IVotingEscrow veCache = IVotingEscrow(votingEscrow);
tokenCache.forceApprove(address(veCache), toVeNFTAmount);
tokenId = veCache.createLockFor(toVeNFTAmount, _LOCK_DURATION, target_, false, withPermanentLock_, managedTokenIdForAttach_);
}
emit Claim(target_, claimAmount, toTokenAmount, toVeNFTAmount, tokenId);
}
/**
* @dev Checks if an address is zero and reverts if it is.
* @param addr_ The address to check.
* @notice Reverts with `AddressZero` if the address is zero.
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert AddressZero();
}
}
function _checkPureTokensRate(uint256 pureTokensRate_) internal pure virtual {
if (pureTokensRate_ > _PRECISION) {
revert IncorrectPureTokensRate();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/bribes/BribeUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {IERC20Upgradeable, SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {IMinter} from "../core/interfaces/IMinter.sol";
import {IVoter} from "../core/interfaces/IVoter.sol";
import {IVotingEscrow} from "../core/interfaces/IVotingEscrow.sol";
import {IBribe} from "./interfaces/IBribe.sol";
import {IBribeFactory} from "./interfaces/IBribeFactory.sol";
import {UpgradeCall} from "../integration/UpgradeCall.sol";
contract BribeUpgradeable is IBribe, ReentrancyGuardUpgradeable, UpgradeCall {
using SafeERC20Upgradeable for IERC20Upgradeable;
uint256 public constant WEEK = 7 days; // rewards are released over 7 days
uint256 public firstBribeTimestamp;
/* ========== STATE VARIABLES ========== */
mapping(address => mapping(uint256 => Reward)) public rewardData; // token -> startTimestamp -> Reward
mapping(address => bool) public isRewardToken;
address[] public rewardTokens;
address public voter;
address public bribeFactory;
address public minter;
address public ve;
string public TYPE;
// owner -> reward token -> lastTime
mapping(address => mapping(address => uint256)) public userRewardPerTokenPaid;
mapping(address => mapping(address => uint256)) public userTimestamp;
//uint256 private _totalSupply;
mapping(uint256 => uint256) internal _totalSupply;
mapping(address => mapping(uint256 => uint256)) internal _balances; //owner -> timestamp -> amount
error RewardClaimPaused();
error RewardClaimNotPaused();
modifier whenNotRewardClaimPaused() {
if (IBribeFactory(bribeFactory).isRewardClaimPause()) {
revert RewardClaimPaused();
}
_;
}
modifier whenRewardClaimPaused() {
if (!IBribeFactory(bribeFactory).isRewardClaimPause()) {
revert RewardClaimNotPaused();
}
_;
}
/* ========== CONSTRUCTOR ========== */
constructor() {
_disableInitializers();
}
function initialize(address _voter, address _bribeFactory, string memory _type) external initializer {
require(_bribeFactory != address(0) && _voter != address(0));
__ReentrancyGuard_init();
voter = _voter;
bribeFactory = _bribeFactory;
firstBribeTimestamp = 0;
ve = IVoter(_voter).votingEscrow();
minter = IVoter(_voter).minter();
require(minter != address(0));
TYPE = _type;
}
/// @notice get the current epoch
function getEpochStart() public view returns (uint256) {
return IMinter(minter).active_period();
}
/// @notice get next epoch (where bribes are saved)
function getNextEpochStart() public view returns (uint256) {
return getEpochStart() + WEEK;
}
/* ========== VIEWS ========== */
/// @notice get the length of the reward tokens
function rewardsListLength() external view returns (uint256) {
return rewardTokens.length;
}
/// @notice get the reward tokens list
function rewardsList() external view returns (address[] memory) {
return rewardTokens;
}
/// @notice get the last totalSupply (total votes for a pool)
function totalSupply() external view returns (uint256) {
uint256 _currentEpochStart = IMinter(minter).active_period(); // claim until current epoch
return _totalSupply[_currentEpochStart];
}
/// @notice get a totalSupply given a timestamp
function totalSupplyAt(uint256 _timestamp) external view returns (uint256) {
return _totalSupply[_timestamp];
}
/// @notice read the balanceOf the tokenId at a given timestamp
function balanceOfAt(uint256 tokenId, uint256 _timestamp) public view returns (uint256) {
address _owner = IVotingEscrow(ve).ownerOf(tokenId);
return _balances[_owner][_timestamp];
}
/// @notice get last deposit available given a tokenID
function balanceOf(uint256 tokenId) public view returns (uint256) {
uint256 _timestamp = getNextEpochStart();
address _owner = IVotingEscrow(ve).ownerOf(tokenId);
return _balances[_owner][_timestamp];
}
/// @notice get the balance of an owner in the current epoch
function balanceOfOwner(address _owner) public view returns (uint256) {
uint256 _timestamp = getNextEpochStart();
return _balances[_owner][_timestamp];
}
/// @notice get the balance of an owner given a timestamp
function balanceOfOwnerAt(address _owner, uint256 _timestamp) public view returns (uint256) {
return _balances[_owner][_timestamp];
}
/// @notice Read earned amount given a tokenID and _rewardToken
function earned(uint256 tokenId, address _rewardToken) public view returns (uint256) {
uint256 k = 0;
uint256 reward = 0;
uint256 _endTimestamp = IMinter(minter).active_period(); // claim until current epoch
address _owner = IVotingEscrow(ve).ownerOf(tokenId);
uint256 _userLastTime = userTimestamp[_owner][_rewardToken];
if (_endTimestamp == _userLastTime) {
return 0;
}
// if user first time then set it to first bribe - week to avoid any timestamp problem
if (_userLastTime < firstBribeTimestamp) {
_userLastTime = firstBribeTimestamp - WEEK;
}
for (k; k < 50; k++) {
if (_userLastTime == _endTimestamp) {
// if we reach the current epoch, exit
break;
}
reward += _earned(_owner, _rewardToken, _userLastTime);
_userLastTime += WEEK;
}
return reward;
}
/// @notice read earned amounts given an address and the reward token
function earned(address _owner, address _rewardToken) public view returns (uint256) {
uint256 k = 0;
uint256 reward = 0;
uint256 _endTimestamp = IMinter(minter).active_period(); // claim until current epoch
uint256 _userLastTime = userTimestamp[_owner][_rewardToken];
if (_endTimestamp == _userLastTime) {
return 0;
}
// if user first time then set it to first bribe - week to avoid any timestamp problem
if (_userLastTime < firstBribeTimestamp) {
_userLastTime = firstBribeTimestamp - WEEK;
}
for (k; k < 50; k++) {
if (_userLastTime == _endTimestamp) {
// if we reach the current epoch, exit
break;
}
reward += _earned(_owner, _rewardToken, _userLastTime);
_userLastTime += WEEK;
}
return reward;
}
/// @notice Read earned amount given address and reward token, returns the rewards and the last user timestamp (used in case user do not claim since 50+epochs)
function earnedWithTimestamp(address _owner, address _rewardToken) private view returns (uint256, uint256) {
uint256 k = 0;
uint256 reward = 0;
uint256 _endTimestamp = IMinter(minter).active_period(); // claim until current epoch
uint256 _userLastTime = userTimestamp[_owner][_rewardToken];
// if user first time then set it to first bribe - week to avoid any timestamp problem
if (_userLastTime < firstBribeTimestamp) {
_userLastTime = firstBribeTimestamp - WEEK;
}
for (k; k < 50; k++) {
if (_userLastTime == _endTimestamp) {
// if we reach the current epoch, exit
break;
}
reward += _earned(_owner, _rewardToken, _userLastTime);
_userLastTime += WEEK;
}
return (reward, _userLastTime);
}
/// @notice get the earned rewards
/// @dev Uses single-step division (balance * rewardsPerEpoch / supply) to avoid
/// intermediate precision loss that occurs with low-decimal tokens (e.g. BTC with 8 decimals).
/// Previously used rewardPerToken() which computed (rewardsPerEpoch * 1e18 / supply),
/// rounding to zero when rewardsPerEpoch * 1e18 < supply.
function _earned(address _owner, address _rewardToken, uint256 _timestamp) internal view returns (uint256) {
uint256 _balance = balanceOfOwnerAt(_owner, _timestamp);
if (_balance == 0) {
return 0;
}
uint256 _supply = _totalSupply[_timestamp];
if (_supply == 0) {
return 0;
}
uint256 _rewardsPerEpoch = rewardData[_rewardToken][_timestamp].rewardsPerEpoch;
return (_balance * _rewardsPerEpoch) / _supply;
}
/* ========== MUTATIVE FUNCTIONS ========== */
/// @notice User votes deposit
/// @dev called on voter.vote() or voter.poke()
/// we save into owner "address" and not "tokenID".
/// Owner must reset before transferring token
function deposit(uint256 amount, uint256 tokenId) external nonReentrant {
require(amount > 0, "Cannot stake 0");
require(msg.sender == voter);
uint256 _startTimestamp = IMinter(minter).active_period();
uint256 _oldSupply = _totalSupply[_startTimestamp];
address _owner = IVotingEscrow(ve).ownerOf(tokenId);
uint256 _lastBalance = _balances[_owner][_startTimestamp];
_totalSupply[_startTimestamp] = _oldSupply + amount;
_balances[_owner][_startTimestamp] = _lastBalance + amount;
emit Staked(tokenId, amount);
}
/// @notice User votes withdrawal
/// @dev called on voter.reset()
function withdraw(uint256 amount, uint256 tokenId) external nonReentrant {
require(amount > 0, "Cannot withdraw 0");
require(msg.sender == voter);
uint256 _startTimestamp = IMinter(minter).active_period();
address _owner = IVotingEscrow(ve).ownerOf(tokenId);
// incase of bribe contract reset in gauge proxy
if (amount <= _balances[_owner][_startTimestamp]) {
uint256 _oldSupply = _totalSupply[_startTimestamp];
uint256 _oldBalance = _balances[_owner][_startTimestamp];
_totalSupply[_startTimestamp] = _oldSupply - amount;
_balances[_owner][_startTimestamp] = _oldBalance - amount;
emit Withdrawn(tokenId, amount);
}
}
/// @notice Claim the TOKENID rewards
function getReward(uint256 tokenId, address[] memory tokens) external nonReentrant whenNotRewardClaimPaused {
require(IVotingEscrow(ve).isApprovedOrOwner(msg.sender, tokenId));
uint256 _userLastTime;
uint256 reward = 0;
address _owner = IVotingEscrow(ve).ownerOf(tokenId);
for (uint256 i = 0; i < tokens.length; i++) {
address _rewardToken = tokens[i];
(reward, _userLastTime) = earnedWithTimestamp(_owner, _rewardToken);
if (reward > 0) {
IERC20Upgradeable(_rewardToken).safeTransfer(_owner, reward);
emit RewardPaid(_owner, _rewardToken, reward);
}
userTimestamp[_owner][_rewardToken] = _userLastTime;
}
}
/// @notice Claim the rewards given msg.sender
function getReward(address[] memory tokens) external nonReentrant whenNotRewardClaimPaused {
uint256 _userLastTime;
uint256 reward = 0;
address _owner = msg.sender;
for (uint256 i = 0; i < tokens.length; i++) {
address _rewardToken = tokens[i];
(reward, _userLastTime) = earnedWithTimestamp(_owner, _rewardToken);
if (reward > 0) {
IERC20Upgradeable(_rewardToken).safeTransfer(_owner, reward);
emit RewardPaid(_owner, _rewardToken, reward);
}
userTimestamp[_owner][_rewardToken] = _userLastTime;
}
}
/// @notice Claim rewards from voter
function getRewardForOwner(uint256 tokenId, address[] memory tokens) public nonReentrant whenNotRewardClaimPaused {
require(msg.sender == voter);
uint256 _userLastTime;
uint256 reward = 0;
address _owner = IVotingEscrow(ve).ownerOf(tokenId);
for (uint256 i = 0; i < tokens.length; i++) {
address _rewardToken = tokens[i];
(reward, _userLastTime) = earnedWithTimestamp(_owner, _rewardToken);
if (reward > 0) {
IERC20Upgradeable(_rewardToken).safeTransfer(_owner, reward);
emit RewardPaid(_owner, _rewardToken, reward);
}
userTimestamp[_owner][_rewardToken] = _userLastTime;
}
}
/// @notice Claim rewards from voter
function getRewardForAddress(address _owner, address[] memory tokens) public nonReentrant whenNotRewardClaimPaused {
require(msg.sender == voter);
uint256 _userLastTime;
uint256 reward = 0;
for (uint256 i = 0; i < tokens.length; i++) {
address _rewardToken = tokens[i];
(reward, _userLastTime) = earnedWithTimestamp(_owner, _rewardToken);
if (reward > 0) {
IERC20Upgradeable(_rewardToken).safeTransfer(_owner, reward);
emit RewardPaid(_owner, _rewardToken, reward);
}
userTimestamp[_owner][_rewardToken] = _userLastTime;
}
}
/// @notice Notify a bribe amount
/// @dev Rewards are saved into NEXT EPOCH mapping.
function notifyRewardAmount(address _rewardsToken, uint256 reward) external nonReentrant {
require(
isRewardToken[_rewardsToken] || IBribeFactory(bribeFactory).isDefaultRewardToken(_rewardsToken),
"reward token not verified"
);
IERC20Upgradeable(_rewardsToken).safeTransferFrom(msg.sender, address(this), reward);
uint256 _startTimestamp = IMinter(minter).active_period(); //period points to the current thursday. Bribes are distributed from next epoch (thursday)
if (firstBribeTimestamp == 0) {
firstBribeTimestamp = _startTimestamp;
}
uint256 _lastReward = rewardData[_rewardsToken][_startTimestamp].rewardsPerEpoch;
rewardData[_rewardsToken][_startTimestamp].rewardsPerEpoch = _lastReward + reward;
rewardData[_rewardsToken][_startTimestamp].lastUpdateTime = block.timestamp;
rewardData[_rewardsToken][_startTimestamp].periodFinish = _startTimestamp + WEEK;
emit RewardAdded(_rewardsToken, reward, _startTimestamp);
}
/* ========== RESTRICTED FUNCTIONS ========== */
function getRewardTokens() external view override returns (address[] memory) {
return IBribeFactory(bribeFactory).getBribeRewardTokens(address(this));
}
function getSpecificRewardTokens() external view override returns (address[] memory) {
uint256 length = rewardTokens.length;
address[] memory tokens = new address[](length);
for (uint256 i; i < length; ) {
tokens[i] = rewardTokens[i];
unchecked {
i++;
}
}
return tokens;
}
/// @notice add rewards tokens
function addRewardTokens(address[] memory _rewardsToken) public onlyAllowed {
uint256 i = 0;
for (i; i < _rewardsToken.length; i++) {
_addRewardToken(_rewardsToken[i]);
}
}
/// @notice add a single reward token
function addRewardToken(address _rewardsToken) public onlyAllowed {
_addRewardToken(_rewardsToken);
}
function _addRewardToken(address _rewardsToken) internal {
if (!isRewardToken[_rewardsToken]) {
isRewardToken[_rewardsToken] = true;
rewardTokens.push(_rewardsToken);
emit AddRewardToken(_rewardsToken);
}
}
/// @notice Recover some ERC20 from the contract and updated given bribe
function recoverERC20AndUpdateData(address tokenAddress, uint256 tokenAmount) external onlyAllowed {
require(tokenAmount <= IERC20Upgradeable(tokenAddress).balanceOf(address(this)));
uint256 _startTimestamp = IMinter(minter).active_period();
uint256 _lastReward = rewardData[tokenAddress][_startTimestamp].rewardsPerEpoch;
rewardData[tokenAddress][_startTimestamp].rewardsPerEpoch = _lastReward - tokenAmount;
rewardData[tokenAddress][_startTimestamp].lastUpdateTime = block.timestamp;
IERC20Upgradeable(tokenAddress).safeTransfer(owner(), tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}
/// @notice Recover some ERC20 from the contract.
/// @dev Be careful --> if called then getReward() at last epoch will fail because some reward are missing!
/// Think about calling recoverERC20AndUpdateData()
function emergencyRecoverERC20(address tokenAddress, uint256 tokenAmount) external onlyAllowed {
require(tokenAmount <= IERC20Upgradeable(tokenAddress).balanceOf(address(this)));
IERC20Upgradeable(tokenAddress).safeTransfer(owner(), tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}
/// @notice Set a new voter
function setVoter(address _Voter) external onlyAllowed {
require(_Voter != address(0));
voter = _Voter;
}
/// @notice Set a new minter
function setMinter(address _minter) external onlyAllowed {
require(_minter != address(0));
minter = _minter;
}
/// @notice Get the current BribeOwner
function owner() public view returns (address) {
return IBribeFactory(bribeFactory).bribeOwner();
}
/* ========== MODIFIERS ========== */
modifier onlyAllowed() {
require((msg.sender == owner() || msg.sender == bribeFactory), "permission is denied!");
_;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@cryptoalgebra/integral-core/contracts/interfaces/vault/IAlgebraVaultFactory.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title The interface for the Algebra Vault Factory
/// @notice This contract can be used for automatic vaults creation
/// @dev Version: Algebra Integral
interface IAlgebraVaultFactory {
/// @notice returns address of the community fee vault for the pool
/// @param pool the address of Algebra Integral pool
/// @return communityFeeVault the address of community fee vault
function getVaultForPool(address pool) external view returns (address communityFeeVault);
/// @notice creates the community fee vault for the pool if needed
/// @param pool the address of Algebra Integral pool
/// @return communityFeeVault the address of community fee vault
function createVaultForPool(address pool) external returns (address communityFeeVault);
}
@openzeppelin/contracts/token/ERC721/ERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)
pragma solidity ^0.8.0;
import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
* the Metadata extension, but not including the Enumerable extension, which is available separately as
* {ERC721Enumerable}.
*/
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
using Address for address;
using Strings for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner or approved for all"
);
_approve(to, tokenId);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* `data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}
/**
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
*/
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _ownerOf(tokenId) != address(0);
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/**
* @dev Safely mints `tokenId` and transfers it to `to`.
*
* Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId, 1);
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
require(!_exists(tokenId), "ERC721: token already minted");
unchecked {
// Will not overflow unless all 2**256 token ids are minted to the same owner.
// Given that tokens are minted one by one, it is impossible in practice that
// this ever happens. Might change if we allow batch minting.
// The ERC fails to describe this case.
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId, 1);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
* This is an internal function that does not check if the sender is authorized to operate on the token.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId, 1);
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
owner = ERC721.ownerOf(tokenId);
// Clear approvals
delete _tokenApprovals[tokenId];
unchecked {
// Cannot overflow, as that would require more tokens to be burned/transferred
// out than the owner initially received through minting and transferring in.
_balances[owner] -= 1;
}
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId, 1);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId, 1);
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[from] -= 1;
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId, 1);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits an {Approval} event.
*/
function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Reverts if the `tokenId` has not been minted yet.
*/
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* The call is not executed if the target address is not a contract.
*
* @param from address representing the previous owner of the given token ID
* @param to target address that will receive the tokens
* @param tokenId uint256 ID of the token to be transferred
* @param data bytes optional data to send along with the call
* @return bool whether the call correctly returned the expected magic value
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
/**
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
* - When `from` is zero, the tokens will be minted for `to`.
* - When `to` is zero, ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
* - When `from` is zero, the tokens were minted for `to`.
* - When `to` is zero, ``from``'s tokens were burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
*
* WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
* being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
* that `ownerOf(tokenId)` is `a`.
*/
// solhint-disable-next-line func-name-mixedcase
function __unsafe_increaseBalance(address account, uint256 amount) internal {
_balances[account] += amount;
}
}
@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/MathUpgradeable.sol";
import "./math/SignedMathUpgradeable.sol";
/**
* @dev String operations.
*/
library StringsUpgradeable {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = MathUpgradeable.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, MathUpgradeable.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
@cryptoalgebra/integral-plugin/contracts/libraries/integration/OracleLibrary.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4;
import '@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol';
import '@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol';
import '@cryptoalgebra/integral-core/contracts/libraries/Plugins.sol';
import '@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol';
import '@cryptoalgebra/integral-periphery/contracts/libraries/PoolAddress.sol';
import '../../interfaces/plugins/IVolatilityOracle.sol';
/// @title Oracle library
/// @notice Provides functions to integrate with Algebra pool TWAP VolatilityOracle
library OracleLibrary {
/// @notice Fetches time-weighted average tick using Algebra VolatilityOracle
/// @param oracleAddress The address of oracle
/// @param period Number of seconds in the past to start calculating time-weighted average
/// @return timeWeightedAverageTick The time-weighted average tick from (block.timestamp - period) to block.timestamp
function consult(address oracleAddress, uint32 period) internal view returns (int24 timeWeightedAverageTick) {
require(period != 0, 'Period is zero');
uint32[] memory secondAgos = new uint32[](2);
secondAgos[0] = period;
secondAgos[1] = 0;
IVolatilityOracle oracle = IVolatilityOracle(oracleAddress);
(int56[] memory tickCumulatives, ) = oracle.getTimepoints(secondAgos);
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
timeWeightedAverageTick = int24(tickCumulativesDelta / int56(uint56(period)));
// Always round to negative infinity
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(uint56(period)) != 0)) timeWeightedAverageTick--;
}
/// @notice Given a tick and a token amount, calculates the amount of token received in exchange
/// @param tick Tick value used to calculate the quote
/// @param baseAmount Amount of token to be converted
/// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination
/// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination
/// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken
function getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken) internal pure returns (uint256 quoteAmount) {
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);
// Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
if (sqrtRatioX96 <= type(uint128).max) {
uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
quoteAmount = baseToken < quoteToken ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192) : FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
} else {
uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64);
quoteAmount = baseToken < quoteToken ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128) : FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
}
}
/// @notice Fetches metadata of last available record (most recent) in oracle
/// @param oracleAddress The address of oracle
/// @return index The index of last available record (most recent) in oracle
/// @return timestamp The timestamp of last available record (most recent) in oracle, truncated to uint32
function lastTimepointMetadata(address oracleAddress) internal view returns (uint16 index, uint32 timestamp) {
index = latestIndex(oracleAddress);
timestamp = IVolatilityOracle(oracleAddress).lastTimepointTimestamp();
}
/// @notice Fetches metadata of oldest available record in oracle
/// @param oracleAddress The address of oracle
/// @return index The index of oldest available record in oracle
/// @return timestamp The timestamp of oldest available record in oracle, truncated to uint32
function oldestTimepointMetadata(address oracleAddress) internal view returns (uint16 index, uint32 timestamp) {
uint16 lastIndex = latestIndex(oracleAddress);
bool initialized;
unchecked {
// overflow is desired
index = lastIndex + 1;
(initialized, timestamp) = timepointMetadata(oracleAddress, index);
}
if (initialized) return (index, timestamp);
(, timestamp) = timepointMetadata(oracleAddress, 0);
return (0, timestamp);
}
/// @notice Gets information about whether the oracle has been initialized
function isInitialized(address oracleAddress) internal view returns (bool result) {
(result, ) = timepointMetadata(oracleAddress, 0);
return result;
}
/// @notice Fetches the index of last available record (most recent) in oracle
function latestIndex(address oracle) internal view returns (uint16) {
return (IVolatilityOracle(oracle).timepointIndex());
}
/// @notice Fetches the metadata of record in oracle
/// @param oracleAddress The address of oracle
/// @param index The index of record in oracle
/// @return initialized Whether or not the timepoint is initialized
/// @return timestamp The timestamp of timepoint
function timepointMetadata(address oracleAddress, uint16 index) internal view returns (bool initialized, uint32 timestamp) {
(initialized, timestamp, , , , , ) = IVolatilityOracle(oracleAddress).timepoints(index);
}
/// @notice Checks if the oracle is currently connected to the pool
/// @param oracleAddress The address of oracle
/// @param oracleAddress The address of the pool
/// @return connected Whether or not the oracle is connected
function isOracleConnectedToPool(address oracleAddress, address poolAddress) internal view returns (bool connected) {
IAlgebraPool pool = IAlgebraPool(poolAddress);
if (oracleAddress == pool.plugin()) {
(, , , uint8 pluginConfig, , ) = pool.globalState();
connected = Plugins.hasFlag(pluginConfig, Plugins.BEFORE_SWAP_FLAG);
}
}
}
@openzeppelin/contracts/access/AccessControl.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
contracts/core/MinterUpgradeable.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity =0.8.19;
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {IMinter} from "./interfaces/IMinter.sol";
import {ILute} from "./interfaces/ILute.sol";
import {IVoter} from "./interfaces/IVoter.sol";
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
// codifies the minting rules as per ve(3,3), abstracted from the token to support any token that allows minting
contract MinterUpgradeable is IMinter, Ownable2StepUpgradeable {
uint256 public constant PRECISION = 10_000; // 10,000 = 100%
uint256 public constant MAX_TEAM_RATE = 500; // 500 bips = 5%
uint256 public constant WEEK = 86400 * 7; // allows minting once per week (reset every Thursday 00:00 UTC)
uint256 public constant TAIL_EMISSION = 20; // 0.2%
uint256 public constant MAX_EMISSION_ADJUSTMENT = 2_500; // +/- 25% bounds expressed in bips
bool public isFirstMint;
bool public isStarted;
uint256 public decayRate;
uint256 public inflationRate;
uint256 public inflationPeriodCount;
uint256 public teamRate;
uint256 public weekly;
uint256 public active_period;
uint256 public lastInflationPeriod;
ILute public lute;
IVoter public voter;
IVotingEscrow public ve;
uint256 public startEmissionDistributionTimestamp;
int256 public epochEmissionAdjustmentBps;
constructor() {
_disableInitializers();
}
function initialize(
address voter_, // the voting & distribution system
address ve_
) external initializer {
__Ownable2Step_init();
isFirstMint = true;
teamRate = MAX_TEAM_RATE;
decayRate = 100; // 1%
inflationRate = 150; // 1.5%
inflationPeriodCount = 12;
active_period = ((block.timestamp + (2 * WEEK)) / WEEK) * WEEK;
weekly = 10_000_000 * 1e18; // represents a starting weekly emission of 10_000_000 Lute (2% from 500_000_000) (Lute has 18 decimals)
lute = ILute(IVotingEscrow(ve_).token());
voter = IVoter(voter_);
ve = IVotingEscrow(ve_);
}
function start() external onlyOwner {
require(!isStarted, "Already started");
isStarted = true;
active_period = ((block.timestamp) / WEEK) * WEEK; // allow minter.update_period() to mint new emissions THIS Thursday
lastInflationPeriod = active_period + inflationPeriodCount * WEEK;
}
function setVoter(address __voter) external onlyOwner {
require(__voter != address(0));
voter = IVoter(__voter);
}
function setVotingEscrow(address votingEscrow_) external onlyOwner {
require(votingEscrow_ != address(0));
ve = IVotingEscrow(votingEscrow_);
}
function setTeamRate(uint256 _teamRate) external onlyOwner {
require(_teamRate <= MAX_TEAM_RATE, "rate too high");
teamRate = _teamRate;
}
function setDecayRate(uint256 _decayRate) external onlyOwner {
require(_decayRate <= PRECISION, "rate too high");
decayRate = _decayRate;
}
function setInflationRate(uint256 _inflationRate) external onlyOwner {
require(_inflationRate <= PRECISION, "rate too high");
inflationRate = _inflationRate;
}
function setEpochEmissionAdjustmentBps(int256 adjustmentBps) external onlyOwner {
require(
adjustmentBps >= -int256(MAX_EMISSION_ADJUSTMENT) && adjustmentBps <= int256(MAX_EMISSION_ADJUSTMENT),
"adjustment bps out of range"
);
epochEmissionAdjustmentBps = adjustmentBps;
emit SetEpochEmissionAdjustmentBps(adjustmentBps);
}
// calculate circulating supply as total token supply - locked supply
function circulating_supply() public view returns (uint256) {
return lute.totalSupply() - lute.balanceOf(address(ve));
}
function circulating_emission() public view returns (uint) {
return (circulating_supply() * TAIL_EMISSION) / PRECISION;
}
function calculate_emission_decay() public view returns (uint256) {
return (weekly * decayRate) / PRECISION;
}
function calculate_emission_inflation() public view returns (uint256) {
return (weekly * inflationRate) / PRECISION;
}
// weekly emission takes the max of calculated (aka target) emission versus circulating tail end emission
function weekly_emission() public view returns (uint256) {
uint256 weeklyCache = weekly;
if (active_period <= lastInflationPeriod) {
return calculate_emission_inflation() + weeklyCache;
} else {
uint256 decay = calculate_emission_decay();
return Math.max(weeklyCache < decay ? 0 : weeklyCache - decay, circulating_emission());
}
}
// update period can only be called once per cycle (1 week)
function update_period() external returns (uint256) {
uint256 _period = active_period;
if (block.timestamp >= _period + WEEK && isStarted) {
// only trigger if new week
_period = (block.timestamp / WEEK) * WEEK;
active_period = _period;
if (block.timestamp >= startEmissionDistributionTimestamp) {
if (!isFirstMint) {
weekly = weekly_emission();
} else {
isFirstMint = false;
}
uint256 weeklyCache = weekly;
int256 epochEmissionAdjustmentBpsCache = epochEmissionAdjustmentBps;
uint256 adjustedWeekly = calculateEmissionWithAdjustment(weeklyCache, epochEmissionAdjustmentBpsCache);
uint256 teamEmissions = (adjustedWeekly * teamRate) / PRECISION;
uint256 gauge = adjustedWeekly - teamEmissions;
uint256 currentBalance = lute.balanceOf(address(this));
if (currentBalance < adjustedWeekly) {
lute.mint(address(this), adjustedWeekly - currentBalance);
}
if(teamEmissions > 0) {
require(lute.transfer(owner(), teamEmissions));
}
lute.approve(address(voter), gauge);
voter.notifyRewardAmount(gauge);
epochEmissionAdjustmentBps = 0;
emit Mint(msg.sender, adjustedWeekly, circulating_supply());
emit Emission(_period, epochEmissionAdjustmentBpsCache, weeklyCache, adjustedWeekly, teamEmissions, gauge);
}
}
return _period;
}
function check() external view returns (bool) {
uint256 _period = active_period;
return (block.timestamp >= _period + WEEK && isStarted);
}
function period() external view returns (uint256) {
return (block.timestamp / WEEK) * WEEK;
}
function calculateEmissionWithAdjustment(uint256 amount, int256 adjustmentBps) public pure returns (uint256) {
if (adjustmentBps == 0) {
return amount;
}
int256 precision = int256(PRECISION);
int256 adjusted = (int256(amount) * (precision + adjustmentBps)) / precision;
if(adjusted < 0) {
return 0;
}
return uint256(adjusted);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}
@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4 <0.9.0;
import '../interfaces/pool/IAlgebraPoolErrors.sol';
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries
library TickMath {
/// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
int24 internal constant MAX_TICK = -MIN_TICK;
/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return price A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
/// at the given tick
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 price) {
unchecked {
// get abs value
int24 absTickMask = tick >> (24 - 1);
uint256 absTick = uint24((tick + absTickMask) ^ absTickMask);
if (absTick > uint24(MAX_TICK)) revert IAlgebraPoolErrors.tickOutOfRange();
uint256 ratio = 0x100000000000000000000000000000000;
if (absTick & 0x1 != 0) ratio = 0xfffcb933bd6fad37aa2d162d1a594001;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick >= 0x40000) {
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
}
if (tick > 0) {
assembly {
ratio := div(not(0), ratio)
}
}
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtRatio of the output price is always consistent
price = uint160((ratio + 0xFFFFFFFF) >> 32);
}
}
/// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
/// @dev Throws in case price < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
/// ever return.
/// @param price The sqrt ratio for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
function getTickAtSqrtRatio(uint160 price) internal pure returns (int24 tick) {
unchecked {
// second inequality must be >= because the price can never reach the price at the max tick
if (price < MIN_SQRT_RATIO || price >= MAX_SQRT_RATIO) revert IAlgebraPoolErrors.priceOutOfRange();
uint256 ratio = uint256(price) << 32;
uint256 r = ratio;
uint256 msb;
assembly {
let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(5, gt(r, 0xFFFFFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(4, gt(r, 0xFFFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(3, gt(r, 0xFF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(2, gt(r, 0xF))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := shl(1, gt(r, 0x3))
msb := or(msb, f)
r := shr(f, r)
}
assembly {
let f := gt(r, 0x1)
msb := or(msb, f)
}
if (msb >= 128) r = ratio >> (msb - 127);
else r = ratio << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= price ? tickHi : tickLow;
}
}
}
@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
import "../extensions/IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20Upgradeable {
using AddressUpgradeable for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20PermitUpgradeable token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));
}
}
contracts/lute/libraries/LibStrategyFlags.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
/**
* @title LibStrategyFlags
* @dev Provides utility functions for managing strategy flags in the context of managed strategies.
*/
library LibStrategyFlags {
/**
* @notice Checks if a given strategy flags set contains a specific flag.
* @dev Uses bitwise operations to determine the presence of a flag within the provided flags set.
* @param strategyFlags_ The set of flags to check
* @param flag_ The specific flag to verify
* @return res True if the flag is present in the set, false otherwise.
*/
function hasFlag(uint8 strategyFlags_, uint256 flag_) internal pure returns (bool res) {
assembly {
res := gt(and(strategyFlags_, flag_), 0)
}
}
/**
* @dev Flag constant used to indicate that restrictions on recovering tokens should be ignored.
*/
uint256 internal constant IGNORE_RESTRICTIONS_ON_RECOVER_TOKENS = 1 << 0;
/**
* @dev Flag constant used to indicate that restrictions on recovering ve nft tokens should be ignored.
*/
uint256 internal constant IGNORE_RESTRICTIONS_ON_RECOVER_VE_NFT_TOKENS = 1 << 1;
/**
* @dev Flag constant used to indicate that restrictions public erc20 compound calls.
*/
uint256 internal constant IGNORE_RESTRICTIONS_ON_PUBLIC_ERC20_COMPOUND = 1 << 2;
/**
* @dev Flag constant used to indicate that restrictions public ve nft compound calls.
*/
uint256 internal constant IGNORE_RESTRICTIONS_ON_PUBLIC_VE_NFT_COMPOUND = 1 << 3;
}
contracts/integration/AlgebraLUTEPriceProviderUpgradeable.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IAlgebraPool} from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import {OracleLibrary} from "@cryptoalgebra/integral-plugin/contracts/libraries/integration/OracleLibrary.sol";
import {IPriceProvider} from "./interfaces/IPriceProvider.sol";
/**
* @title AlgebraLUTEPriceProviderUpgradeable-UNSAFE
* @notice This contract provides the price of the LUTE token in USD by querying an Algebra-based pool.
*
* It is marked as UNSAFE because it allows price manipulation through the referenced pool. Users should
* be aware that the price returned by `getUsdToLUTEPrice` can be influenced by actions within the Algebra pool,
* potentially leading to inaccurate or manipulated price data.
*
* IMPORTANT: The contract is intended for use in places where manipulating the price in the pool is more expensive
* than the profit generated. And also where price manipulation is not profitable
*
* @dev Provides price data for LUTE token in USD, utilizing an Algebra-based pool for price calculation.
* Designed to be upgradeable using OpenZeppelin's upgradeable contracts framework.
*/
contract AlgebraLUTEPriceProviderUpgradeable is IPriceProvider, Initializable {
// errors
error PoolIsLocked();
error UnsafeCast();
error AddressZero();
/**
* @dev Return the value of one USD in the smallest unit based on the USD token's decimals.
*/
uint256 public ONE_USD;
/**
* @dev Return address of the LUTE token
*/
address public LUTE;
/**
* @dev Return address of the USD token
*/
address public USD;
/**
* @dev Return address of the Algebra pool used for price calculation.
*/
address public pool;
/**
* @dev Initializes the contract by disabling the initializer of the inherited upgradeable contract.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the price provider with the addresses of the Algebra pool, LUTE token, and USD token.
* @dev Stores the necessary addresses for later use and sets ONE_USD based on the USD token's decimals.
* @param pool_ Address of the Algebra pool used for price calculations.
* @param LUTE_ Address of the LUTE token.
* @param USD_ Address of the USD token.
*/
function initialize(address pool_, address LUTE_, address USD_) external initializer {
_checkAddressZero(pool_);
_checkAddressZero(LUTE_);
_checkAddressZero(USD_);
pool = pool_;
LUTE = LUTE_;
USD = USD_;
ONE_USD = 10 ** IERC20Metadata(USD_).decimals();
}
/**
* @notice Retrieves the current price of 1 USD in LUTE tokens, according to the specified Algebra pool.
* @dev Queries the current tick from the Algebra pool to calculate the price. The price can be manipulated through actions within the pool, so it should be used with caution.
* @return Price of 1 USD in LUTE tokens.
*/
function getUsdToLUTEPrice() external view override returns (uint256) {
return OracleLibrary.getQuoteAtTick(currentTick(), _toUint128(ONE_USD), USD, LUTE);
}
/**
* @dev Retrieves the current tick from the Algebra pool.
* @return The current tick of the pool.
*/
function currentTick() public view returns (int24) {
(, int24 tick, , , , bool unlocked) = IAlgebraPool(pool).globalState();
if (unlocked) {
return tick;
}
revert PoolIsLocked();
}
/**
* @dev Converts a uint256 to a uint128, ensuring there is no overflow.
* @param y The uint256 to convert.
* @return z The converted uint128.
*/
function _toUint128(uint256 y) internal pure returns (uint128 z) {
z = uint128(y);
if (z != y) {
revert UnsafeCast();
}
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/core/RLute.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ERC20, ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
import {IRLute} from "./interfaces/IRLute.sol";
/**
* @title RLute Token Contract
* @dev Implementation of the rLUTE token, an ERC20 token with convert features.
* Inherits functionality from OpenZeppelin's ERC20Burnable and Ownable2Step contracts.
* Provides mechanisms for token conversion and owner interaction.
*/
contract RLute is IRLute, ERC20Burnable, Ownable2Step {
using SafeERC20 for IERC20;
uint256 internal constant _PRECISION = 1e18; // Precision for percentage calculations
uint256 internal constant _LOCK_DURATION = 182 days; // Lock duration for veLUTE tokens
uint256 internal constant _TO_TOKEN_PERCENTAGE = 0.4e18; // Percentage of rLUTE converted to LUTE
address public override votingEscrow; // Address of the Voting Escrow contract for veLUTE
address public override token; // Address of the LUTE token
error AddressZero();
/**
* @dev Initializes the contract by setting the governance, token, and Voting Escrow addresses.
* @param votingEscrow_ Address of the Voting Escrow contract.
*/
constructor(address votingEscrow_) ERC20("rLUTE", "rLUTE") {
_checkAddressZero(votingEscrow_);
address tokenTemp = IVotingEscrow(votingEscrow_).token();
_checkAddressZero(tokenTemp);
token = tokenTemp;
votingEscrow = votingEscrow_;
}
/**
* @notice Converts all rLUTE tokens of the caller to LUTE and veLUTE tokens.
* Burns rLUTE tokens and mints LUTE and veLUTE tokens proportionally.
*/
function convertAll() external override {
_convert(balanceOf(msg.sender));
}
/**
* @notice Converts a specific amount of rLUTE tokens of the caller to LUTE and veLUTE tokens.
* @param amount_ The amount of rLUTE tokens to convert.
* Burns the specified amount of rLUTE tokens and mints LUTE and veLUTE tokens proportionally.
*/
function convert(uint256 amount_) external override {
_convert(amount_);
}
/**
* @notice Allows the owner to recover LUTE tokens from the contract.
* @param amount_ The amount of LUTE tokens to be recovered.
* Transfers the specified amount of LUTE tokens to the owner's address.
*/
function recoverToken(uint256 amount_) external onlyOwner {
IERC20(token).safeTransfer(msg.sender, amount_);
emit Recover(msg.sender, amount_);
}
/**
* @notice Mints rLUTE tokens to a specified address.
* @param to_ The address to receive the minted tokens.
* @param amount_ The amount of tokens to mint.
*/
function mint(address to_, uint256 amount_) external onlyOwner {
_mint(to_, amount_);
}
/**
* @dev Internal function to handle the conversion of rLUTE to LUTE and veLUTE.
* @param amount_ The amount of rLUTE to convert.
*/
function _convert(uint256 amount_) internal {
if (amount_ == 0) {
revert ZERO_AMOUNT();
}
_burn(msg.sender, amount_);
IERC20 tokenCache = IERC20(token);
uint256 toTokenAmount = (amount_ * _TO_TOKEN_PERCENTAGE) / _PRECISION;
uint256 toVeNFTAmount = amount_ - toTokenAmount;
uint256 tokenId;
if (toVeNFTAmount > 0) {
IVotingEscrow veCache = IVotingEscrow(votingEscrow);
tokenCache.forceApprove(address(veCache), toVeNFTAmount);
tokenId = veCache.createLockFor(toVeNFTAmount, _LOCK_DURATION, msg.sender, false, false, 0);
tokenCache.forceApprove(address(veCache), 0);
}
if (toTokenAmount > 0) {
tokenCache.safeTransfer(msg.sender, toTokenAmount);
}
emit Converted(msg.sender, amount_, toTokenAmount, toVeNFTAmount, tokenId);
}
/**
* @dev Checks if an address is zero and reverts if true.
* @param addr_ The address to check.
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
}
contracts/bribes/interfaces/IBribe.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IBribe {
struct Reward {
uint256 periodFinish;
uint256 rewardsPerEpoch;
uint256 lastUpdateTime;
}
/* ========== EVENTS ========== */
event RewardAdded(address indexed rewardToken, uint256 reward, uint256 startTimestamp);
event Staked(uint256 indexed tokenId, uint256 amount);
event Withdrawn(uint256 indexed tokenId, uint256 amount);
event RewardPaid(address indexed user, address indexed rewardsToken, uint256 reward);
event Recovered(address indexed token, uint256 amount);
event AddRewardToken(address indexed token);
function deposit(uint amount, uint tokenId) external;
function withdraw(uint amount, uint tokenId) external;
function getRewardTokens() external view returns (address[] memory);
function getSpecificRewardTokens() external view returns (address[] memory);
function getRewardForOwner(uint tokenId, address[] memory tokens) external;
function getRewardForAddress(address _owner, address[] memory tokens) external;
function notifyRewardAmount(address token, uint amount) external;
function addRewardToken(address) external;
function addRewardTokens(address[] memory) external;
function initialize(address, address, string memory) external;
function firstBribeTimestamp() external view returns (uint256);
function totalSupplyAt(uint256 timestamp) external view returns (uint256);
function rewardData(address, uint256) external view returns (uint256 periodFinish, uint256 rewardsPerEpoch, uint256 lastUpdateTime);
function rewardsListLength() external view returns (uint256);
function getEpochStart() external view returns (uint256);
function earned(uint256 tokenId, address _rewardToken) external view returns (uint256);
function earned(address _owner, address _rewardToken) external view returns (uint256);
function balanceOfAt(uint256 tokenId, uint256 _timestamp) external view returns (uint256);
function balanceOf(uint256 tokenId) external view returns (uint256);
function getNextEpochStart() external view returns (uint256);
}
@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)
pragma solidity ^0.8.0;
import "../ERC20.sol";
import "../../../utils/Context.sol";
/**
* @dev Extension of {ERC20} that allows token holders to destroy both their own
* tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis).
*/
abstract contract ERC20Burnable is Context, ERC20 {
/**
* @dev Destroys `amount` tokens from the caller.
*
* See {ERC20-_burn}.
*/
function burn(uint256 amount) public virtual {
_burn(_msgSender(), amount);
}
/**
* @dev Destroys `amount` tokens from `account`, deducting from the caller's
* allowance.
*
* See {ERC20-_burn} and {ERC20-allowance}.
*
* Requirements:
*
* - the caller must have allowance for ``accounts``'s tokens of at least
* `amount`.
*/
function burnFrom(address account, uint256 amount) public virtual {
_spendAllowance(account, _msgSender(), amount);
_burn(account, amount);
}
}
contracts/mocks/WETH9.sol
pragma solidity =0.8.19;
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
// function() public payable {
// deposit();
// }
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad, "");
balanceOf[msg.sender] -= wad;
payable(msg.sender).transfer(wad);
emit Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return address(this).balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad) public returns (bool) {
require(balanceOf[src] >= wad, "");
if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
require(allowance[src][msg.sender] >= wad, "");
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
emit Transfer(src, dst, wad);
return true;
}
}
contracts/core/libraries/LibVotingEscrowUtils.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./LibVotingEscrowConstants.sol";
library LibVotingEscrowUtils {
/**
* @notice Internal function to get the maximum unlock timestamp.
* @return The maximum unlock timestamp.
*/
function maxUnlockTimestamp() internal view returns (uint256) {
return roundToWeek(block.timestamp + MAX_LOCK_TIME);
}
/**
* @notice Internal function to round a timestamp to the nearest week.
* @param time_ The timestamp to round.
* @return The rounded timestamp.
*/
function roundToWeek(uint256 time_) internal pure returns (uint256) {
return (time_ / WEEK) * WEEK;
}
/**
* @notice Internal function to convert a uint256 amount to an int128.
* @param amount_ The amount to convert.
* @return The converted amount.
*/
function toInt128(uint256 amount_) internal pure returns (int128) {
return int128(int256(amount_));
}
/**
* @notice Internal function to convert an int128 amount to a uint256.
* @param amount_ The amount to convert.
* @return The converted amount.
*/
function toUint256(int128 amount_) internal pure returns (uint256) {
return uint256(int256(amount_));
}
}
@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
StringsUpgradeable.toHexString(account),
" is missing role ",
StringsUpgradeable.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
contracts/core/CompoundEmissionExtensionUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import "./interfaces/ICompoundEmissionExtension.sol";
import "./interfaces/IVoter.sol";
import "./interfaces/IVotingEscrow.sol";
import "../bribes/interfaces/IBribe.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
/**
* @title CompoundEmissionExtensionUpgradeable
* @notice
* This contract serves as an extension on top of a Voter contract to automatically
* compound user emissions into veNFT locks and/or Bribe pools. Users may configure
* how their claimed emissions are allocated among:
* 1) Multiple veNFT locks (via TargetLock[]).
* 2) Multiple bribe pools (via TargetPool[]).
*
* The user can define what fraction (percentage) of their emissions goes to locks
* and what fraction goes to bribe pools. Each fraction’s distribution can further be
* split across multiple targets (locks and/or bribe pools).
*
* The contract also supports creating or depositing into veNFT locks with configurable
* lock parameters, either via a user-specific config or a global default config.
*
* @dev
* - Inherits from {ReentrancyGuardUpgradeable} to protect state-mutating functions
* from reentrancy attacks.
* - Relies on the Voter’s roles to manage who can call certain functions.
* Specifically, only addresses with the COMPOUND_KEEPER_ROLE can perform batch
* compounding on behalf of users.
*/
contract CompoundEmissionExtensionUpgradeable is ICompoundEmissionExtension, ReentrancyGuardUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @notice Precision factor for percentage calculations (1e18 = 100%).
*/
uint256 internal constant _PRECISION = 1e18;
/**
* @notice Role for the keeper responsible for triggering emission compounding
* (e.g., on a regular schedule).
*/
bytes32 public constant COMPOUND_KEEPER_ROLE = keccak256("COMPOUND_KEEPER_ROLE");
/**
* @notice Role for the administrator with permissions to set default create-lock configurations.
*/
bytes32 public constant COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE = keccak256("COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE");
/**
* @notice The address of the Voter contract from which emissions will be claimed.
*/
address public voter;
/**
* @notice The token being locked (and compounded) in the VotingEscrow contract.
*/
address public token;
/**
* @notice The VotingEscrow contract address where emissions are locked.
*/
address public votingEscrow;
/**
* @notice The default configuration for creating new locks if a user has not set a custom config.
*
* @dev
* - `shouldBoosted` Whether to treat the lock as boosted.
* - `withPermanentLock` Whether this lock is permanently locked.
* - `lockDuration` The duration (in seconds) for the lock (ignored if `withPermanentLock = true`).
* - `managedTokenIdForAttach` An optional existing managed veNFT ID to which this deposit is attached.
*/
CreateLockConfig public defaultCreateLockConfig;
/**
* @notice For each user, the fraction of emissions that should be deposited into veNFT locks (in 1e18 = 100%).
*/
mapping(address => uint256) public getToLocksPercentage;
/**
* @notice For each user, the fraction of emissions that should be deposited into bribe pools (in 1e18 = 100%).
*/
mapping(address => uint256) public getToBribePoolsPercentage;
/**
* @notice Indicates whether a user has a custom `CreateLockConfig` set.
*/
mapping(address => bool) internal _usersCreateLockConfigIsEnable;
/**
* @notice The user’s custom `CreateLockConfig`, if `_usersCreateLockConfigIsEnable[user]` is true.
*/
mapping(address => CreateLockConfig) internal _usersCreateLockConfigs;
/**
* @notice Defines how a user’s allocated portion for veNFT locks is further split among multiple locks.
*
* @dev Each entry includes a `tokenId` of an existing veNFT lock and a `percentage` (1e18 = 100%)
* indicating how that portion is distributed. All `TargetLock[]` for a user must sum to 1e18
* if the user has a nonzero `getToLocksPercentage[user]`.
*/
mapping(address => TargetLock[]) internal _usersCompoundEmissionTargetLocks;
/**
* @notice Defines how a user’s allocated portion for bribe pools is further split among multiple pools.
*
* @dev Each entry includes an address of the pool, and a `percentage` (1e18 = 100%)
* indicating how that portion is distributed. All `TargetPool[]` for a user must sum to 1e18
* if the user has a nonzero `getToBribePoolsPercentage[user]`.
*/
mapping(address => TargetPool[]) internal _usersCompoundEmissionTargetBribesPools;
/**
* @notice Thrown when an invalid lock configuration is provided.
*/
error InvalidCreateLockConfig();
/**
* @notice Thrown when the user sets an invalid emission compounding parameter combination.
*/
error InvalidCompoundEmissionParams();
/**
* @notice Thrown when attempting to set token locks for a veNFT that does not belong to the user.
*/
error AnotherUserTargetLocks();
/**
* @notice Thrown when access is denied for the operation.
*/
error AccessDenied();
/**
* @notice Thrown when attempting to set a target bribe pool that is associated with a killed gauge.
*/
error TargetPoolGaugeIsKilled();
/**
* @dev Restricts execution to addresses holding the specified role in the Voter contract.
* @param role_ The role required for the function call.
*/
modifier onlyRole(bytes32 role_) {
if (!IVoter(voter).hasRole(role_, msg.sender)) {
revert AccessDenied();
}
_;
}
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the compound emission extension (called once).
* @dev
* - Sets default lock duration to approximately 6 months (15724800 seconds).
* - Should be invoked right after deployment.
*
* @param voter_ The address of the Voter contract.
* @param token_ The address of the token being locked in VotingEscrow.
* @param votingEscrow_ The address of the VotingEscrow contract.
*/
function initialize(address voter_, address token_, address votingEscrow_) external initializer {
__ReentrancyGuard_init();
voter = voter_;
token = token_;
votingEscrow = votingEscrow_;
defaultCreateLockConfig = CreateLockConfig(false, false, 15724800, 0);
}
/**
* @notice Sets the global default create-lock configuration. This applies to any user that does not have a custom config.
* @dev Only callable by addresses with the COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE role.
* @param config_ The new default `CreateLockConfig`.
*
* Requirements:
* - `config_.lockDuration` must be nonzero if `withPermanentLock` is false.
* - At least one of `withPermanentLock`, `lockDuration`, or `managedTokenIdForAttach` must be set if `shouldBoosted` is false.
*
* Emits a {SetDefaultCreateLockConfig} event.
*/
function setDefaultCreateLockConfig(
CreateLockConfig calldata config_
) external onlyRole(COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE) {
if (!config_.withPermanentLock && config_.lockDuration == 0 && config_.managedTokenIdForAttach == 0 && !config_.shouldBoosted) {
revert InvalidCreateLockConfig();
}
if (config_.lockDuration == 0 && !config_.withPermanentLock) {
revert InvalidCreateLockConfig();
}
defaultCreateLockConfig = config_;
emit SetDefaultCreateLockConfig(config_);
}
/**
* @notice Sets or removes a user-specific `CreateLockConfig`.
* @dev
* - If all parameters in `config_` are zero/false, the user's config is removed,
* reverting them to using the default config.
* - Otherwise, the config must be valid (nonzero lock duration if `withPermanentLock` = false).
*
* @param config_ The `CreateLockConfig` for the caller (`msg.sender`).
*
* Emits a {SetCreateLockConfig} event.
*/
function setCreateLockConfig(CreateLockConfig calldata config_) external {
if (!config_.withPermanentLock && config_.lockDuration == 0 && config_.managedTokenIdForAttach == 0 && !config_.shouldBoosted) {
delete _usersCreateLockConfigIsEnable[msg.sender];
delete _usersCreateLockConfigs[msg.sender];
} else {
if (config_.lockDuration == 0 && !config_.withPermanentLock) {
revert InvalidCreateLockConfig();
}
_usersCreateLockConfigIsEnable[msg.sender] = true;
_usersCreateLockConfigs[msg.sender] = config_;
}
emit SetCreateLockConfig(msg.sender, config_);
}
/**
* @notice Updates the user’s emission compounding configuration, including:
* - Percentages allocated to locks vs. bribe pools.
* - The specific lock targets (`TargetLock[]`).
* - The specific bribe pool targets (`TargetPool[]`).
*
* @dev
* - The total of `toLocksPercentage + toBribePoolsPercentage` cannot exceed 1e18 (100%).
* - If `toLocksPercentage > 0`, then there must be a nonempty `targetLocks` array (and vice versa).
* - If `toBribePoolsPercentage > 0`, then there must be a nonempty `targetsBribePools` array (and vice versa).
* - The sum of all `percentage` fields in `targetLocks` must be exactly 1e18 if updating them.
* - The sum of all `percentage` fields in `targetsBribePools` must be exactly 1e18 if updating them.
* - Each `targetLocks[i].tokenId` must belong to the caller if nonzero.
* - Each `targetsBribePools[i].pool` must correspond to a gauge that is alive.
*
* @param p_ A struct with the following fields:
* - `shouldUpdateGeneralPercentages` Whether to update the overall splits to locks/bribe pools.
* - `shouldUpdateTargetLocks` Whether to replace the entire array of user’s `TargetLock[]`.
* - `shouldUpdateTargetBribePools` Whether to replace the entire array of user’s `TargetPool[]`.
* - `toLocksPercentage` The fraction of user’s emissions allocated to locks (1e18 = 100%).
* - `toBribePoolsPercentage` The fraction of user’s emissions allocated to bribe pools (1e18 = 100%).
* - `targetLocks` The new `TargetLock[]`, each with a `tokenId` and `percentage`.
* - `targetsBribePools` The new `TargetPool[]`, each with a `pool` and `percentage`.
*
* Emits {SetCompoundEmissionGeneralPercentages} if `shouldUpdateGeneralPercentages` is true.
* Emits {SetCompoundEmissionTargetLocks} if `shouldUpdateTargetLocks` is true.
* Emits {SetCompoundEmissionTargetBribePools} if `shouldUpdateTargetBribePools` is true.
*
* Reverts with {InvalidCompoundEmissionParams} if the inputs fail the above constraints.
*/
function setCompoundEmissionConfig(UpdateCompoundEmissionConfigParams calldata p_) external {
uint256 newTargetLocksLength = p_.shouldUpdateTargetLocks
? p_.targetLocks.length
: _usersCompoundEmissionTargetLocks[msg.sender].length;
uint256 newTargetBribePoolsLength = p_.shouldUpdateTargetBribePools
? p_.targetsBribePools.length
: _usersCompoundEmissionTargetBribesPools[msg.sender].length;
uint256 newToTargetLocksPercentage = p_.shouldUpdateGeneralPercentages ? p_.toLocksPercentage : getToLocksPercentage[msg.sender];
uint256 newToTargetBribePoolsPercentage = p_.shouldUpdateGeneralPercentages
? p_.toBribePoolsPercentage
: getToBribePoolsPercentage[msg.sender];
if (newToTargetLocksPercentage + newToTargetBribePoolsPercentage > _PRECISION) {
revert InvalidCompoundEmissionParams();
}
if (
(newToTargetLocksPercentage > 0 && newTargetLocksLength == 0) || (newToTargetLocksPercentage == 0 && newTargetLocksLength > 0)
) {
revert InvalidCompoundEmissionParams();
}
if (
(newToTargetBribePoolsPercentage > 0 && newTargetBribePoolsLength == 0) ||
(newToTargetBribePoolsPercentage == 0 && newTargetBribePoolsLength > 0)
) {
revert InvalidCompoundEmissionParams();
}
if (p_.shouldUpdateTargetLocks && newToTargetLocksPercentage > 0) {
IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
uint256 targetLocksSumPercentage;
for (uint256 i; i < p_.targetLocks.length; ) {
uint256 percentage = p_.targetLocks[i].percentage;
if (p_.targetLocks[i].tokenId != 0) {
if (votingEscrowCache.ownerOf(p_.targetLocks[i].tokenId) != msg.sender) {
revert AnotherUserTargetLocks();
}
}
if (percentage == 0) {
revert InvalidCompoundEmissionParams();
}
targetLocksSumPercentage += p_.targetLocks[i].percentage;
unchecked {
i++;
}
}
if (targetLocksSumPercentage != _PRECISION) {
revert InvalidCompoundEmissionParams();
}
_usersCompoundEmissionTargetLocks[msg.sender] = p_.targetLocks;
emit SetCompoundEmissionTargetLocks(msg.sender, p_.targetLocks);
}
if (p_.shouldUpdateTargetBribePools && newToTargetBribePoolsPercentage > 0) {
IVoter voterCache = IVoter(voter);
uint256 targetBribePoolsSumPercentage;
for (uint256 i; i < p_.targetsBribePools.length; ) {
address targetPool = p_.targetsBribePools[i].pool;
uint256 percentage = p_.targetsBribePools[i].percentage;
if (targetPool == address(0) || percentage == 0) {
revert InvalidCompoundEmissionParams();
}
address gauge = voterCache.poolToGauge(targetPool);
if (!voterCache.isAlive(gauge)) {
revert TargetPoolGaugeIsKilled();
}
targetBribePoolsSumPercentage += percentage;
unchecked {
i++;
}
}
if (targetBribePoolsSumPercentage != _PRECISION) {
revert InvalidCompoundEmissionParams();
}
_usersCompoundEmissionTargetBribesPools[msg.sender] = p_.targetsBribePools;
emit SetCompoundEmissionTargetBribePools(msg.sender, p_.targetsBribePools);
}
if (p_.shouldUpdateGeneralPercentages) {
if (newToTargetLocksPercentage == 0) {
delete _usersCompoundEmissionTargetLocks[msg.sender];
emit SetCompoundEmissionTargetLocks(msg.sender, p_.targetLocks);
}
if (newToTargetBribePoolsPercentage == 0) {
delete _usersCompoundEmissionTargetBribesPools[msg.sender];
emit SetCompoundEmissionTargetBribePools(msg.sender, p_.targetsBribePools);
}
getToLocksPercentage[msg.sender] = newToTargetLocksPercentage;
getToBribePoolsPercentage[msg.sender] = newToTargetBribePoolsPercentage;
emit SetCompoundEmissionGeneralPercentages(msg.sender, p_.toLocksPercentage, p_.toBribePoolsPercentage);
}
}
/**
* @notice Retrieves the effective `CreateLockConfig` for a user, falling back to `defaultCreateLockConfig` if none is set.
* @param target_ The address of the user.
* @return createLockConfig The effective config for the user (custom if set, otherwise default).
*/
function getUserCreateLockConfig(address target_) public view returns (CreateLockConfig memory createLockConfig) {
return _usersCreateLockConfigIsEnable[target_] ? _usersCreateLockConfigs[target_] : defaultCreateLockConfig;
}
/**
* @notice Retrieves a user’s overall emission-compounding configuration.
* @param target_ The address of the user.
* @return toLocksPercentage Fraction allocated to veNFT locks (1e18=100%).
* @return toBribePoolsPercentage Fraction allocated to bribe pools (1e18=100%).
* @return isCreateLockCustomConfig Whether the user has a custom `CreateLockConfig`.
* @return createLockConfig The effective `CreateLockConfig` (custom or default).
* @return targetLocks The user’s `TargetLock[]` array.
* @return targetBribePools The user’s `TargetPool[]` array.
*/
function getUserInfo(
address target_
)
external
view
returns (
uint256 toLocksPercentage,
uint256 toBribePoolsPercentage,
bool isCreateLockCustomConfig,
CreateLockConfig memory createLockConfig,
TargetLock[] memory targetLocks,
TargetPool[] memory targetBribePools
)
{
toLocksPercentage = getToLocksPercentage[target_];
toBribePoolsPercentage = getToBribePoolsPercentage[target_];
createLockConfig = getUserCreateLockConfig(target_);
targetLocks = _usersCompoundEmissionTargetLocks[target_];
isCreateLockCustomConfig = _usersCreateLockConfigIsEnable[target_];
targetBribePools = _usersCompoundEmissionTargetBribesPools[target_];
}
/**
* @notice Batch operation for compounding emissions for multiple users.
* @dev
* - Only callable by addresses with the COMPOUND_KEEPER_ROLE.
* - Processes each user’s claim in a single transaction.
*
* @param claimsParams_ An array of `ClaimParams` describing each user's claim details:
* - `target`: The user whose emissions are being claimed.
* - `gauges`: The array of gauge addresses to claim from.
* - `blaze`: Optional data for Blaze-based claims (if applicable).
*/
function compoundEmissionClaimBatch(ClaimParams[] calldata claimsParams_) external onlyRole(COMPOUND_KEEPER_ROLE) nonReentrant {
for (uint256 i; i < claimsParams_.length; ) {
_compoundEmissionClaim(claimsParams_[i]);
unchecked {
i++;
}
}
}
/**
* @notice Allows a user to directly compound their emissions for the specified gauges.
* @dev
* - Only callable by the user themselves (`claimParams_.target`).
*
* @param claimParams_ The `ClaimParams` struct:
* - `target`: The user who is claiming.
* - `gauges`: The array of gauge addresses to claim from.
* - `blaze`: Optional data for blaze-based claims.
*/
function compoundEmisisonClaim(ClaimParams calldata claimParams_) external nonReentrant {
_checkSender(claimParams_.target);
_compoundEmissionClaim(claimParams_);
}
/**
* @notice Disambiguates changes to a user’s `TargetLock[]` token IDs in case of merges or transfers.
* @dev
* - If multiple entries reference `targetTokenId_`, all will be updated to `newTokenId_`.
* - If `newTokenId_ = 0`, these entries are cleared, meaning a new veNFT can be created
* next time if that portion is used for compounding.
* - Typically called by the Voter after a veNFT merge or transfer event.
*
* @param target_ The user whose `TargetLock[]` to update.
* @param targetTokenId_ The old token ID in the user’s array.
* @param newTokenId_ The new token ID to replace the old one (0 if removing).
*
* Emits a {ChangeEmissionTargetLock} event whenever a replacement occurs.
*/
function changeEmissionTargetLockId(address target_, uint256 targetTokenId_, uint256 newTokenId_) external nonReentrant {
_checkSender(voter);
if (getToLocksPercentage[target_] > 0) {
TargetLock[] memory targetLocks = _usersCompoundEmissionTargetLocks[target_];
for (uint256 i; i < targetLocks.length; ) {
if (targetLocks[i].tokenId == targetTokenId_) {
_usersCompoundEmissionTargetLocks[target_][i].tokenId = newTokenId_;
emit ChangeEmissionTargetLock(target_, targetTokenId_, newTokenId_);
}
unchecked {
i++;
}
}
}
}
/**
* @notice Calculates how much of `amountIn_` would be allocated to locks vs. bribe pools for a given user.
* @dev Does not factor in how that portion is further split among multiple `TargetLock[]` or `TargetPool[]`.
* @param target_ The user in question.
* @param amountIn_ The total amount of tokens to be distributed for the user.
* @return toTargetLocks The portion allocated to veNFT locks.
* @return toTargetBribePools The portion allocated to bribe pools.
*/
function getAmountOutToCompound(
address target_,
uint256 amountIn_
) external view returns (uint256 toTargetLocks, uint256 toTargetBribePools) {
toTargetLocks = (getToLocksPercentage[target_] * amountIn_) / _PRECISION;
toTargetBribePools = (getToBribePoolsPercentage[target_] * amountIn_) / _PRECISION;
}
/**
* @dev Core logic to compound the user’s claimed emissions into veNFT locks
* and/or bribe pools, as dictated by their configuration.
*
* Steps:
* 1) Call Voter to claim the user’s emissions from specified gauges.
* 2) Transfer those claimed tokens from Voter to this contract.
* 3) Based on user’s config, deposit the appropriate amounts into:
* (a) veNFT locks (creating new or depositing into existing).
* (b) Bribe pools.
*
* @param claimParams_ The user’s claim info from {ClaimParams}.
*/
function _compoundEmissionClaim(ClaimParams calldata claimParams_) internal {
IVoter voterCache = IVoter(voter);
(uint256 toTargetLocks, uint256 toTargetBribePools) = voterCache.onCompoundEmissionClaim(
claimParams_.target,
claimParams_.gauges,
claimParams_.blaze
);
if (toTargetLocks + toTargetBribePools == 0) {
return;
}
IERC20Upgradeable tokenCache = IERC20Upgradeable(token);
IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
CreateLockConfig memory userCreateLockConfig = getUserCreateLockConfig(claimParams_.target);
tokenCache.safeTransferFrom(address(voterCache), address(this), toTargetLocks + toTargetBribePools);
if (toTargetLocks > 0) {
tokenCache.forceApprove(address(votingEscrowCache), toTargetLocks);
TargetLock[] memory targetLocks = _usersCompoundEmissionTargetLocks[claimParams_.target];
uint256 length = targetLocks.length;
for (uint256 i; i < length; ) {
TargetLock memory targetLock = targetLocks[i];
uint256 amount = (targetLock.percentage * toTargetLocks) / _PRECISION;
if (targetLock.tokenId == 0) {
targetLock.tokenId = votingEscrowCache.createLockFor(
amount,
userCreateLockConfig.lockDuration,
claimParams_.target,
userCreateLockConfig.shouldBoosted,
userCreateLockConfig.withPermanentLock,
userCreateLockConfig.managedTokenIdForAttach
);
_usersCompoundEmissionTargetLocks[claimParams_.target][i].tokenId = targetLock.tokenId;
emit CreateLockFromCompoundEmission(claimParams_.target, targetLock.tokenId, amount);
} else {
votingEscrowCache.depositFor(targetLock.tokenId, amount, false, false);
emit CompoundEmissionToTargetLock(claimParams_.target, targetLock.tokenId, amount);
}
unchecked {
i++;
}
}
}
if (toTargetBribePools > 0) {
TargetPool[] memory targetBribePools = _usersCompoundEmissionTargetBribesPools[claimParams_.target];
uint256 length = targetBribePools.length;
for (uint256 i; i < length; ) {
TargetPool memory targetPool = targetBribePools[i];
address gauge = voterCache.poolToGauge(targetPool.pool);
uint256 amount = (targetPool.percentage * toTargetBribePools) / _PRECISION;
if (voterCache.isAlive(gauge)) {
address externalBribe = voterCache.getGaugeState(gauge).externalBribe;
tokenCache.forceApprove(externalBribe, amount);
IBribe(externalBribe).notifyRewardAmount(address(tokenCache), amount);
emit CompoundEmissionToBribePool(claimParams_.target, targetPool.pool, amount);
} else {
tokenCache.forceApprove(address(votingEscrowCache), amount);
uint256 tokenId = votingEscrowCache.createLockFor(
amount,
userCreateLockConfig.lockDuration,
claimParams_.target,
userCreateLockConfig.shouldBoosted,
userCreateLockConfig.withPermanentLock,
userCreateLockConfig.managedTokenIdForAttach
);
emit CreateLockFromCompoundEmissionForBribePools(claimParams_.target, targetPool.pool, tokenId, amount);
}
unchecked {
i++;
}
}
}
}
/**
* @dev Checks that `msg.sender` matches `expected_`; otherwise reverts with {AccessDenied}.
* @param expected_ The address required for the operation.
*/
function _checkSender(address expected_) internal view {
if (msg.sender != expected_) {
revert AccessDenied();
}
}
}
contracts/mocks/VoterEscrowMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract VoterEscrowMock {
address public token;
function setToken(address token_) external {
token = token_;
}
}
contracts/bribes/interfaces/IBribeFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IBribeFactory {
/**
* @dev Emitted when the voter address is updated. This address is used for voting in fee vaults.
*
* @param oldVoter The address of the previous voter.
* @param newVoter The address of the new voter that has been set.
*/
event SetVoter(address indexed oldVoter, address indexed newVoter);
event bribeImplementationChanged(address _oldbribeImplementation, address _newbribeImplementation);
event AddDefaultRewardToken(address indexed token);
event RemoveDefaultRewardToken(address indexed token);
event PauseRewardClaim(bool indexed isPaused_);
function createBribe(address _token0, address _token1, string memory _type) external returns (address);
function bribeImplementation() external view returns (address impl);
function bribeOwner() external view returns (address owner);
function isDefaultRewardToken(address token_) external view returns (bool);
function getDefaultRewardTokens() external view returns (address[] memory);
function getBribeRewardTokens(address bribe_) external view returns (address[] memory);
function isRewardClaimPause() external view returns (bool);
}
contracts/mocks/NumberFormatterMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {NumberFormatter} from "../core/libraries/NumberFormatter.sol";
contract NumberFormatterMock {
function formatNumber(uint256 number_, uint8 decimals_, uint8 limitFactionNumbers_) external pure returns (string memory) {
return NumberFormatter.formatNumber(number_, decimals_, limitFactionNumbers_);
}
function withThousandSeparators(uint256 value_) external pure returns (string memory) {
return NumberFormatter.withThousandSeparators(value_);
}
function toStringWithLeadingZeros(uint256 value_, uint8 decimals_) external pure returns (string memory) {
return NumberFormatter.toStringWithLeadingZeros(value_, decimals_);
}
function limitFactionNumbers(string memory strValue, uint8 limit) external pure returns (string memory) {
return NumberFormatter.limitFactionNumbers(strValue, limit);
}
}
contracts/core/libraries/DateTime.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Copy from https://github.com/RollaProject/solidity-datetime/blob/master/contracts/DateTime.sol
// ----------------------------------------------------------------------------
// DateTime Library v2.0
//
// A gas-efficient Solidity date and time library
//
// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary
//
// Tested date range 1970/01/01 to 2345/12/31
//
// Conventions:
// Unit | Range | Notes
// :-------- |:-------------:|:-----
// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC
// year | 1970 ... 2345 |
// month | 1 ... 12 |
// day | 1 ... 31 |
// hour | 0 ... 23 |
// minute | 0 ... 59 |
// second | 0 ... 59 |
// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday
//
//
// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence.
// ----------------------------------------------------------------------------
library DateTime {
uint256 constant SECONDS_PER_DAY = 24 * 60 * 60;
uint256 constant SECONDS_PER_HOUR = 60 * 60;
uint256 constant SECONDS_PER_MINUTE = 60;
int256 constant OFFSET19700101 = 2440588;
uint256 constant DOW_MON = 1;
uint256 constant DOW_TUE = 2;
uint256 constant DOW_WED = 3;
uint256 constant DOW_THU = 4;
uint256 constant DOW_FRI = 5;
uint256 constant DOW_SAT = 6;
uint256 constant DOW_SUN = 7;
// ------------------------------------------------------------------------
// Calculate the number of days from 1970/01/01 to year/month/day using
// the date conversion algorithm from
// http://aa.usno.navy.mil/faq/docs/JD_Formula.php
// and subtracting the offset 2440588 so that 1970/01/01 is day 0
//
// days = day
// - 32075
// + 1461 * (year + 4800 + (month - 14) / 12) / 4
// + 367 * (month - 2 - (month - 14) / 12 * 12) / 12
// - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4
// - offset
// ------------------------------------------------------------------------
function _daysFromDate(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 _days) {
require(year >= 1970);
int256 _year = int256(year);
int256 _month = int256(month);
int256 _day = int256(day);
int256 __days = _day -
32075 +
(1461 * (_year + 4800 + (_month - 14) / 12)) /
4 +
(367 * (_month - 2 - ((_month - 14) / 12) * 12)) /
12 -
(3 * ((_year + 4900 + (_month - 14) / 12) / 100)) /
4 -
OFFSET19700101;
_days = uint256(__days);
}
// ------------------------------------------------------------------------
// Calculate year/month/day from the number of days since 1970/01/01 using
// the date conversion algorithm from
// http://aa.usno.navy.mil/faq/docs/JD_Formula.php
// and adding the offset 2440588 so that 1970/01/01 is day 0
//
// int L = days + 68569 + offset
// int N = 4 * L / 146097
// L = L - (146097 * N + 3) / 4
// year = 4000 * (L + 1) / 1461001
// L = L - 1461 * year / 4 + 31
// month = 80 * L / 2447
// dd = L - 2447 * month / 80
// L = month / 11
// month = month + 2 - 12 * L
// year = 100 * (N - 49) + year + L
// ------------------------------------------------------------------------
function _daysToDate(uint256 _days) internal pure returns (uint256 year, uint256 month, uint256 day) {
unchecked {
int256 __days = int256(_days);
int256 L = __days + 68569 + OFFSET19700101;
int256 N = (4 * L) / 146097;
L = L - (146097 * N + 3) / 4;
int256 _year = (4000 * (L + 1)) / 1461001;
L = L - (1461 * _year) / 4 + 31;
int256 _month = (80 * L) / 2447;
int256 _day = L - (2447 * _month) / 80;
L = _month / 11;
_month = _month + 2 - 12 * L;
_year = 100 * (N - 49) + _year + L;
year = uint256(_year);
month = uint256(_month);
day = uint256(_day);
}
}
function timestampFromDate(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 timestamp) {
timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY;
}
function timestampFromDateTime(
uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
) internal pure returns (uint256 timestamp) {
timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + hour * SECONDS_PER_HOUR + minute * SECONDS_PER_MINUTE + second;
}
function timestampToDate(uint256 timestamp) internal pure returns (uint256 year, uint256 month, uint256 day) {
unchecked {
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
}
function timestampToDateTime(
uint256 timestamp
) internal pure returns (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) {
unchecked {
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
uint256 secs = timestamp % SECONDS_PER_DAY;
hour = secs / SECONDS_PER_HOUR;
secs = secs % SECONDS_PER_HOUR;
minute = secs / SECONDS_PER_MINUTE;
second = secs % SECONDS_PER_MINUTE;
}
}
function isValidDate(uint256 year, uint256 month, uint256 day) internal pure returns (bool valid) {
if (year >= 1970 && month > 0 && month <= 12) {
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > 0 && day <= daysInMonth) {
valid = true;
}
}
}
function isValidDateTime(
uint256 year,
uint256 month,
uint256 day,
uint256 hour,
uint256 minute,
uint256 second
) internal pure returns (bool valid) {
if (isValidDate(year, month, day)) {
if (hour < 24 && minute < 60 && second < 60) {
valid = true;
}
}
}
function isLeapYear(uint256 timestamp) internal pure returns (bool leapYear) {
(uint256 year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY);
leapYear = _isLeapYear(year);
}
function _isLeapYear(uint256 year) internal pure returns (bool leapYear) {
leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) {
weekDay = getDayOfWeek(timestamp) <= DOW_FRI;
}
function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) {
weekEnd = getDayOfWeek(timestamp) >= DOW_SAT;
}
function getDaysInMonth(uint256 timestamp) internal pure returns (uint256 daysInMonth) {
(uint256 year, uint256 month, ) = _daysToDate(timestamp / SECONDS_PER_DAY);
daysInMonth = _getDaysInMonth(year, month);
}
function _getDaysInMonth(uint256 year, uint256 month) internal pure returns (uint256 daysInMonth) {
if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
daysInMonth = 31;
} else if (month != 2) {
daysInMonth = 30;
} else {
daysInMonth = _isLeapYear(year) ? 29 : 28;
}
}
// 1 = Monday, 7 = Sunday
function getDayOfWeek(uint256 timestamp) internal pure returns (uint256 dayOfWeek) {
uint256 _days = timestamp / SECONDS_PER_DAY;
dayOfWeek = ((_days + 3) % 7) + 1;
}
function getYear(uint256 timestamp) internal pure returns (uint256 year) {
(year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
function getMonth(uint256 timestamp) internal pure returns (uint256 month) {
(, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
function getDay(uint256 timestamp) internal pure returns (uint256 day) {
(, , day) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
function getHour(uint256 timestamp) internal pure returns (uint256 hour) {
uint256 secs = timestamp % SECONDS_PER_DAY;
hour = secs / SECONDS_PER_HOUR;
}
function getMinute(uint256 timestamp) internal pure returns (uint256 minute) {
uint256 secs = timestamp % SECONDS_PER_HOUR;
minute = secs / SECONDS_PER_MINUTE;
}
function getSecond(uint256 timestamp) internal pure returns (uint256 second) {
second = timestamp % SECONDS_PER_MINUTE;
}
function addYears(uint256 timestamp, uint256 _years) internal pure returns (uint256 newTimestamp) {
(uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY);
year += _years;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
require(newTimestamp >= timestamp);
}
function addMonths(uint256 timestamp, uint256 _months) internal pure returns (uint256 newTimestamp) {
(uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY);
month += _months;
year += (month - 1) / 12;
month = ((month - 1) % 12) + 1;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
require(newTimestamp >= timestamp);
}
function addDays(uint256 timestamp, uint256 _days) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp + _days * SECONDS_PER_DAY;
require(newTimestamp >= timestamp);
}
function addHours(uint256 timestamp, uint256 _hours) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp + _hours * SECONDS_PER_HOUR;
require(newTimestamp >= timestamp);
}
function addMinutes(uint256 timestamp, uint256 _minutes) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE;
require(newTimestamp >= timestamp);
}
function addSeconds(uint256 timestamp, uint256 _seconds) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp + _seconds;
require(newTimestamp >= timestamp);
}
function subYears(uint256 timestamp, uint256 _years) internal pure returns (uint256 newTimestamp) {
(uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY);
year -= _years;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
require(newTimestamp <= timestamp);
}
function subMonths(uint256 timestamp, uint256 _months) internal pure returns (uint256 newTimestamp) {
(uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY);
uint256 yearMonth = year * 12 + (month - 1) - _months;
year = yearMonth / 12;
month = (yearMonth % 12) + 1;
uint256 daysInMonth = _getDaysInMonth(year, month);
if (day > daysInMonth) {
day = daysInMonth;
}
newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
require(newTimestamp <= timestamp);
}
function subDays(uint256 timestamp, uint256 _days) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp - _days * SECONDS_PER_DAY;
require(newTimestamp <= timestamp);
}
function subHours(uint256 timestamp, uint256 _hours) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp - _hours * SECONDS_PER_HOUR;
require(newTimestamp <= timestamp);
}
function subMinutes(uint256 timestamp, uint256 _minutes) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE;
require(newTimestamp <= timestamp);
}
function subSeconds(uint256 timestamp, uint256 _seconds) internal pure returns (uint256 newTimestamp) {
newTimestamp = timestamp - _seconds;
require(newTimestamp <= timestamp);
}
function diffYears(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _years) {
require(fromTimestamp <= toTimestamp);
(uint256 fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY);
(uint256 toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
_years = toYear - fromYear;
}
function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _months) {
require(fromTimestamp <= toTimestamp);
(uint256 fromYear, uint256 fromMonth, ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY);
(uint256 toYear, uint256 toMonth, ) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
_months = toYear * 12 + toMonth - fromYear * 12 - fromMonth;
}
function diffDays(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _days) {
require(fromTimestamp <= toTimestamp);
_days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY;
}
function diffHours(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _hours) {
require(fromTimestamp <= toTimestamp);
_hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR;
}
function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _minutes) {
require(fromTimestamp <= toTimestamp);
_minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE;
}
function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _seconds) {
require(fromTimestamp <= toTimestamp);
_seconds = toTimestamp - fromTimestamp;
}
}
contracts/mocks/CompoundEmissionExtensionUpgradeableMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "../core/CompoundEmissionExtensionUpgradeable.sol";
contract CompoundEmissionExtensionUpgradeableMock is CompoundEmissionExtensionUpgradeable {
constructor() CompoundEmissionExtensionUpgradeable() {}
function mock_setupVoter(address voter_) external {
voter = voter_;
}
}
@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../StringsUpgradeable.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSAUpgradeable {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32")
mstore(0x1c, hash)
message := keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", StringsUpgradeable.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x00", validator, data));
}
}
contracts/mocks/MerkleDistributionCreatorShortMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import "../integration/interfaces/IDistributionCreator.sol";
import {IERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
error CampaignDoesNotExist();
error CampaignAlreadyExists();
error CampaignDurationBelowHour();
error CampaignRewardTokenNotWhitelisted();
error CampaignRewardTooLow();
error CampaignSouldStartInFuture();
error InvalidDispute();
error InvalidLengths();
error InvalidParam();
error InvalidParams();
error InvalidProof();
error InvalidUninitializedRoot();
error InvalidReward();
error InvalidSignature();
error NoDispute();
error NotGovernor();
error NotGovernorOrGuardian();
error NotSigned();
error NotTrusted();
error NotWhitelisted();
error UnresolvedDispute();
error ZeroAddress();
struct CampaignParameters {
// POPULATED ONCE CREATED
// ID of the campaign. This can be left as a null bytes32 when creating campaigns
// on Merkl.
bytes32 campaignId;
// CHOSEN BY CAMPAIGN CREATOR
// Address of the campaign creator, if marked as address(0), it will be overriden with the
// address of the `msg.sender` creating the campaign
address creator;
// Address of the token used as a reward
address rewardToken;
// Amount of `rewardToken` to distribute across all the epochs
// Amount distributed per epoch is `amount/numEpoch`
uint256 amount;
// Type of campaign
uint32 campaignType;
// Timestamp at which the campaign should start
uint32 startTimestamp;
// Duration of the campaign in seconds. Has to be a multiple of EPOCH = 3600
uint32 duration;
// Extra data to pass to specify the campaign
bytes campaignData;
}
contract MerkleDistributionCreatorMock {
uint32 public constant HOUR = 3600;
mapping(bytes32 => uint256) internal _campaignLookup;
address public feeRecipient;
address public distributor;
mapping(address => uint256) public isWhitelistedToken;
mapping(address => uint256) public rewardTokenMinAmounts;
CampaignParameters[] public campaignList;
uint256 public immutable CHAIN_ID = block.chainid;
uint256 public constant BASE_9 = 1e9;
mapping(uint32 => uint256) public campaignSpecificFees;
event DistributorUpdated(address indexed _distributor);
event FeeRebateUpdated(address indexed user, uint256 userFeeRebate);
event FeeRecipientUpdated(address indexed _feeRecipient);
event FeesSet(uint256 _fees);
event CampaignSpecificFeesSet(uint32 campaignType, uint256 _fees);
event MessageUpdated(bytes32 _messageHash);
event NewCampaign(CampaignParameters campaign);
event NewDistribution(DistributionParameters distribution, address indexed sender);
event RewardTokenMinimumAmountUpdated(address indexed token, uint256 amount);
event TokenWhitelistToggled(address indexed token, uint256 toggleStatus);
event UserSigned(bytes32 messageHash, address indexed user);
event UserSigningWhitelistToggled(address indexed user, uint256 toggleStatus);
mapping(address => uint256) public feeRebate;
uint256 public defaultFees = 1e8;
function toggleTokenWhitelist(address token) external {
uint256 toggleStatus = 1 - isWhitelistedToken[token];
isWhitelistedToken[token] = toggleStatus;
}
function acceptConditions() external {}
function setRewardTokenMinAmounts(address[] calldata tokens, uint256[] calldata amounts) external {
uint256 tokensLength = tokens.length;
for (uint256 i; i < tokensLength; ++i) {
uint256 amount = amounts[i];
rewardTokenMinAmounts[tokens[i]] = amount;
}
}
function createDistribution(DistributionParameters memory newDistribution) external returns (uint256) {
IERC20(newDistribution.rewardToken).transferFrom(msg.sender, address(this), newDistribution.amount);
}
}
contracts/mocks/VirtualRewarderCheckpointsMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {VirtualRewarderCheckpoints} from "../lute/libraries/VirtualRewarderCheckpoints.sol";
contract VirtualRewarderCheckpointsMock {
mapping(uint256 index => VirtualRewarderCheckpoints.Checkpoint checkpoint) public checkpoints;
function writeCheckpoint(uint256 lastIndex_, uint256 timestamp_, uint256 amount_) external returns (uint256) {
return VirtualRewarderCheckpoints.writeCheckpoint(checkpoints, lastIndex_, timestamp_, amount_);
}
function getCheckpointIndex(uint256 lastIndex_, uint256 timestamp_) external view returns (uint256) {
return VirtualRewarderCheckpoints.getCheckpointIndex(checkpoints, lastIndex_, timestamp_);
}
function getAmount(uint256 lastIndex_, uint256 timestamp_) external view returns (uint256) {
return VirtualRewarderCheckpoints.getAmount(checkpoints, lastIndex_, timestamp_);
}
}
contracts/lute/interfaces/IRouterV2PathProvider.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IRouterV2} from "../../dexV2/interfaces/IRouterV2.sol";
/**
* @title Interface for Router V2 Path Provider
* @notice Defines the required functionalities for managing routing paths within a decentralized exchange.
*/
interface IRouterV2PathProvider {
/**
* @notice Emitted when a new route is registered for a token
* @param token Address of the token for which the route is registered
* @param route Details of the registered route
*/
event RegisterRouteForToken(address indexed token, IRouterV2.route route);
/**
* @notice Emitted when the allowance of a token to be used in input routes is updated
* @param token Address of the token
* @param isAllowed New allowance status (true if allowed, false otherwise)
*/
event SetAllowedTokenInInputRouters(address indexed token, bool indexed isAllowed);
/**
* @notice Emitted when a new route is added to a token
* @param token Address of the token to which the route is added
* @param route Details of the route added
*/
event AddRouteToToken(address indexed token, IRouterV2.route route);
/**
* @notice Emitted when a route is removed from a token
* @param token Address of the token from which the route is removed
* @param route Details of the route removed
*/
event RemoveRouteFromToken(address indexed token, IRouterV2.route route);
/**
* @notice Sets whether a token can be used in input routes
* @param token_ Address of the token to set the permission
* @param isAllowed_ Boolean flag to allow or disallow the token
*/
function setAllowedTokenInInputRouters(address token_, bool isAllowed_) external;
/**
* @notice Fetches the address of the router
* @return The address of the router contract
*/
function router() external view returns (address);
/**
* @notice Fetches the address of the factory
* @return The address of the factory contract
*/
function factory() external view returns (address);
/**
* @notice Checks if a token is allowed in input routes
* @param token_ Address of the token to check
* @return True if the token is allowed, false otherwise
*/
function isAllowedTokenInInputRoutes(address token_) external view returns (bool);
/**
* @notice Retrieves all possible routes between two tokens
* @param inputToken_ Address of the input token
* @param outputToken_ Address of the output token
* @return routes A two-dimensional array of routes
*/
function getRoutesTokenToToken(address inputToken_, address outputToken_) external view returns (IRouterV2.route[][] memory routes);
/**
* @notice Determines the optimal route and output amount for a given input amount between two tokens
* @param inputToken_ Address of the input token
* @param outputToken_ Address of the output token
* @param amountIn_ Amount of the input token
* @return A tuple containing the optimal route and the output amount
*/
function getOptimalTokenToTokenRoute(
address inputToken_,
address outputToken_,
uint256 amountIn_
) external view returns (IRouterV2.route[] memory, uint256 amountOut);
/**
* @notice Calculates the output amount for a specified route given an input amount
* @param amountIn_ Amount of input tokens
* @param routes_ Routes to calculate the output amount
* @return The amount of output tokens
*/
function getAmountOutQuote(uint256 amountIn_, IRouterV2.route[] calldata routes_) external view returns (uint256);
/**
* @notice Validates if all routes provided are valid according to the system's rules
* @param inputRouters_ Array of routes to validate
* @return True if all routes are valid, false otherwise
*/
function isValidInputRoutes(IRouterV2.route[] calldata inputRouters_) external view returns (bool);
}
contracts/integration/OpenOceanVeNftDirectBuyer.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import "./interfaces/IOpenOceanVeNftDirectBuyer.sol";
import "../core/interfaces/IVotingEscrow.sol";
/**
* @title OpenOceanVeNftDirectBuyer
* @notice This contract facilitates the direct purchase of veNFTs via token swaps on the OpenOcean exchange.
* @dev Integrates with OpenOcean, VotingEscrow to manage direct purchases of veNFTs.
*/
contract OpenOceanVeNftDirectBuyer is IOpenOceanVeNftDirectBuyer, Ownable {
using SafeERC20 for IERC20;
/**
* @notice The address of the OpenOcean exchange contract used for token swaps.
*/
address public openOceanExchange;
/**
* @notice The address of the VotingEscrow contract used to create veNFTs.
*/
address public votingEscrow;
/**
* @notice The address of the ERC20 token used for veNFT creation.
*/
address public token;
/**
* @notice Thrown when the output token amount received from the swap does not match expectations.
*/
error InvalidOutputAmount();
/**
* @notice Thrown when the destination token in the swap is not the expected token for veNFT creation.
*/
error InvalidDstToken();
/**
* @notice Thrown when a token attempts to use permit functionality, which is not supported.
*/
error PermitNotSupported();
/**
* @notice Thrown when the swap's destination receiver address is invalid.
*/
error InvalidDstReceiver();
error AddressZero();
/**
* @notice Initializes the contract with required addresses.
* @param votingEscrow_ Address of the VotingEscrow contract.
* @param token_ Address of the token used for veNFT creation.
* @param openOceanExchange_ Address of the OpenOcean exchange contract.
*/
constructor(address votingEscrow_, address token_, address openOceanExchange_) {
if (votingEscrow_ == address(0) || token_ == address(0) || openOceanExchange_ == address(0)) {
revert AddressZero();
}
votingEscrow = votingEscrow_;
token = token_;
openOceanExchange = openOceanExchange_;
}
/**
* @notice Allows the owner to rescue tokens or ETH
* @param token_ The ERC20 token to be rescued.
*/
function rescueFunds(IERC20 token_) external onlyOwner {
_transfer(token_, payable(_msgSender()), _getBalance(token_, address(this)));
}
/**
* @notice Facilitates a direct purchase of veNFTs by performing a token swap and veNFT creation.
* @dev The function validates inputs, executes the swap, and creates a veNFT for the recipient.
* @param caller_ The OpenOcean caller contract.
* @param desc_ The swap description containing details of the source and destination tokens.
* @param calls_ The calls to execute as part of the OpenOcean swap.
* @param votingEscrowCreateForParams_ Parameters for creating the veNFT.
* @return tokenAmount The amount of destination tokens obtained in the swap.
* @return tokenId The ID of the veNFT created.
* @custom:requirements The destination token must match the expected token, and the caller must provide sufficient balance.
* @custom:emits Emits a `DirectVeNftPurchase` event on successful veNFT creation.
*/
function directVeNftPurchase(
IOpenOceanCaller caller_,
IOpenOceanExchange.SwapDescription calldata desc_,
IOpenOceanCaller.CallDescription[] calldata calls_,
VotingEscrowCreateLockForParams calldata votingEscrowCreateForParams_
) external payable virtual override returns (uint256 tokenAmount, uint256 tokenId) {
IERC20 tokenCache = IERC20(token);
if (address(desc_.dstToken) != address(tokenCache)) {
revert InvalidDstToken();
}
if (desc_.dstReceiver != address(this) && desc_.dstReceiver != address(0)) {
revert InvalidDstReceiver();
}
IOpenOceanExchange openOceanExchangeCache = IOpenOceanExchange(openOceanExchange);
uint256 srcTokenInitialBalance = _getBalance(desc_.srcToken, address(this));
if (_isETH(desc_.srcToken)) {
srcTokenInitialBalance -= msg.value;
} else {
if (desc_.permit.length > 0) {
revert PermitNotSupported();
}
desc_.srcToken.safeTransferFrom(_msgSender(), address(this), desc_.amount);
desc_.srcToken.forceApprove(address(openOceanExchangeCache), desc_.amount);
}
uint256 dstTokenInitialBalance = tokenCache.balanceOf(address(this));
tokenAmount = IOpenOceanExchange(openOceanExchange).swap{value: msg.value}(caller_, desc_, calls_);
if ((tokenCache.balanceOf(address(this)) - dstTokenInitialBalance) != tokenAmount) {
revert InvalidOutputAmount();
}
uint256 srcTokenRestBalance = _getBalance(desc_.srcToken, address(this)) - srcTokenInitialBalance;
if (srcTokenRestBalance > 0) {
_transfer(desc_.srcToken, _msgSender(), srcTokenRestBalance);
}
IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
tokenCache.forceApprove(address(votingEscrowCache), tokenAmount);
tokenId = votingEscrowCache.createLockFor(
tokenAmount,
votingEscrowCreateForParams_.lockDuration,
votingEscrowCreateForParams_.to,
votingEscrowCreateForParams_.shouldBoosted,
votingEscrowCreateForParams_.withPermanentLock,
votingEscrowCreateForParams_.managedTokenIdForAttach
);
emit DirectVeNftPurchase(
_msgSender(),
votingEscrowCreateForParams_.to,
address(desc_.srcToken),
desc_.amount - srcTokenRestBalance,
tokenAmount,
tokenId
);
}
/**
* @notice Internal function to transfer tokens or ETH.
* @param token_ The ERC20 token or ETH to transfer.
* @param target_ The recipient address.
* @param amount_ The amount to transfer.
*/
function _transfer(IERC20 token_, address target_, uint256 amount_) internal {
if (_isETH(token_)) {
Address.sendValue(payable(target_), amount_);
} else {
token_.safeTransfer(target_, amount_);
}
}
/**
* @notice Internal function to get the balance of a token or ETH.
* @param token_ The ERC20 token or ETH to check.
* @param target_ The address to check the balance for.
* @return The balance of the token or ETH.
*/
function _getBalance(IERC20 token_, address target_) internal view returns (uint256) {
return _isETH(token_) ? target_.balance : token_.balanceOf(target_);
}
/**
* @notice Checks whether the token is ETH.
* @param token_ The token to check.
* @return True if the token is ETH, otherwise false.
*/
function _isETH(IERC20 token_) internal pure returns (bool) {
return address(token_) == address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) || address(token_) == address(0);
}
}
contracts/bribes/BribeFactoryUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {BribeProxy} from "./BribeProxy.sol";
import {IBribe} from "./interfaces/IBribe.sol";
import {IBribeFactory} from "./interfaces/IBribeFactory.sol";
contract BribeFactoryUpgradeable is IBribeFactory, OwnableUpgradeable {
address public last_bribe;
address public voter;
address public bribeImplementation;
address[] public defaultRewardToken;
mapping(address => bool) public override isDefaultRewardToken;
bool public override isRewardClaimPause;
error AddressZero();
constructor() {
_disableInitializers();
}
function initialize(address _voter, address _bribeImplementation) external initializer {
_checkAddressZero(_voter);
_checkAddressZero(_bribeImplementation);
__Ownable_init();
voter = _voter;
bribeImplementation = _bribeImplementation;
}
function createBribe(address _token0, address _token1, string memory _type) external returns (address) {
require(msg.sender == voter || msg.sender == owner(), "only voter or voter");
address newLastBribe = address(new BribeProxy());
IBribe(newLastBribe).initialize(voter, address(this), _type);
if (_token0 != address(0)) IBribe(newLastBribe).addRewardToken(_token0);
if (_token1 != address(0)) IBribe(newLastBribe).addRewardToken(_token1);
IBribe(newLastBribe).addRewardTokens(defaultRewardToken);
last_bribe = newLastBribe;
return newLastBribe;
}
function setRewardClaimPause(bool isPaused_) external onlyOwner {
isRewardClaimPause = isPaused_;
emit PauseRewardClaim(isPaused_);
}
function bribeOwner() external view returns (address) {
return owner();
}
function changeImplementation(address _implementation) external onlyOwner {
_checkAddressZero(_implementation);
require(_implementation != address(0));
emit bribeImplementationChanged(bribeImplementation, _implementation);
bribeImplementation = _implementation;
}
/**
* @dev Sets the address used for voting in the fee vaults. Only callable by the contract owner.
*
* @param voter_ The new voter address to be set.
*/
function setVoter(address voter_) external virtual onlyOwner {
_checkAddressZero(voter_);
emit SetVoter(voter, voter_);
voter = voter_;
}
function addRewards(address _token, address[] memory _bribes) external onlyOwner {
for (uint256 i; i < _bribes.length; ) {
IBribe(_bribes[i]).addRewardToken(_token);
unchecked {
i++;
}
}
}
function addRewards(address[][] memory _token, address[] memory _bribes) external {
require(msg.sender == voter || msg.sender == owner(), "only voter or owner");
require(_token.length == _bribes.length, "arraies length mismatch");
for (uint256 i; i < _bribes.length; ) {
IBribe(_bribes[i]).addRewardTokens(_token[i]);
unchecked {
i++;
}
}
}
function getDefaultRewardTokens() external view returns (address[] memory) {
uint256 length = defaultRewardToken.length;
address[] memory tokens = new address[](length);
for (uint256 i; i < length; ) {
tokens[i] = defaultRewardToken[i];
unchecked {
i++;
}
}
return tokens;
}
function getBribeRewardTokens(address bribe_) external view returns (address[] memory) {
address[] memory bribeRewardsTokens = IBribe(bribe_).getSpecificRewardTokens();
uint256 length = defaultRewardToken.length;
address[] memory tokens = new address[](length + bribeRewardsTokens.length);
for (uint256 i; i < length; ) {
tokens[i] = defaultRewardToken[i];
unchecked {
i++;
}
}
for (uint256 i; i < bribeRewardsTokens.length; ) {
tokens[i + length] = bribeRewardsTokens[i];
unchecked {
i++;
}
}
return tokens;
}
/// @notice set the bribe factory permission registry
function pushDefaultRewardToken(address _token) external onlyOwner {
_checkAddressZero(_token);
require(!isDefaultRewardToken[_token], "already added");
defaultRewardToken.push(_token);
isDefaultRewardToken[_token] = true;
emit AddDefaultRewardToken(_token);
}
/// @notice set the bribe factory permission registry
function removeDefaultRewardToken(address _token) external onlyOwner {
_checkAddressZero(_token);
uint i = 0;
for (i; i < defaultRewardToken.length; i++) {
if (defaultRewardToken[i] == _token) {
defaultRewardToken[i] = defaultRewardToken[defaultRewardToken.length - 1];
defaultRewardToken.pop();
isDefaultRewardToken[_token] = false;
emit RemoveDefaultRewardToken(_token);
return;
}
}
revert("not exists");
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
@openzeppelin/contracts/interfaces/IERC1967.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*
* _Available since v4.8.3._
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}
contracts/utils/PairAPIUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {
IERC20Upgradeable,
IERC20MetadataUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import "../core/interfaces/IVoter.sol";
import "../core/interfaces/IVotingEscrow.sol";
import "../dexV2/interfaces/IPairFactory.sol";
import "../dexV2/interfaces/IPair.sol";
import "../gauges/interfaces/IGauge.sol";
import "../bribes/interfaces/IBribe.sol";
contract PairAPIUpgradeable is OwnableUpgradeable {
struct pairInfo {
// pair info
address pair_address; // pair contract address
string symbol; // pair symbol
string name; // pair name
uint decimals; // pair decimals
bool stable; // pair pool type (stable = false, means it's a variable type of pool)
uint total_supply; // pair tokens supply
address clPool;
uint feeAmount;
// token pair info
address token0; // pair 1st token address
string token0_symbol; // pair 1st token symbol
uint token0_decimals; // pair 1st token decimals
uint reserve0; // pair 1st token reserves (nr. of tokens in the contract)
uint claimable0; // claimable 1st token from fees (for unstaked positions)
address token1; // pair 2nd token address
string token1_symbol; // pair 2nd token symbol
uint token1_decimals; // pair 2nd token decimals
uint reserve1; // pair 2nd token reserves (nr. of tokens in the contract)
uint claimable1; // claimable 2nd token from fees (for unstaked positions)
// pairs gauge
address gauge; // pair gauge address
uint gauge_total_supply; // pair staked tokens (less/eq than/to pair total supply)
uint gauge_total_weight; // pair total weight of staked tokens (less/eq than/to pair total supply)
address fee; // pair fees contract address
address bribe; // pair bribes contract address
uint emissions; // pair emissions (per second)
address emissions_token; // pair emissions token address
uint emissions_token_decimals; // pair emissions token decimals
// User deposit
uint account_lp_balance; // account LP tokens balance
uint account_token0_balance; // account 1st token balance
uint account_token1_balance; // account 2nd token balance
uint account_gauge_balance; // account pair staked in gauge balance
uint account_gauge_total_weight; // account pair total Weight of all NFT gauge
uint account_gauge_earned; // account earned emissions for this pair
uint _a0Expect;
uint _a1Expect;
}
struct tokenBribe {
address token;
uint8 decimals;
uint256 amount;
string symbol;
}
struct pairBribeEpoch {
uint256 epochTimestamp;
uint256 totalVotes;
address pair;
tokenBribe[] bribes;
}
uint256 public constant MAX_PAIRS = 1000;
uint256 public constant MAX_EPOCHS = 200;
uint256 public constant MAX_REWARDS = 16;
uint256 public constant WEEK = 7 * 24 * 60 * 60;
IPairFactory public pairFactory;
IVoter public voter;
address public underlyingToken;
event Owner(address oldOwner, address newOwner);
event Voter(address oldVoter, address newVoter);
event WBF(address oldWBF, address newWBF);
constructor() {
_disableInitializers();
}
function initialize(address voter_) public initializer {
__Ownable_init();
voter = IVoter(voter_);
pairFactory = IPairFactory(IVoter(voter_).v2PoolFactory());
underlyingToken = IVotingEscrow(IVoter(voter_).votingEscrow()).token();
}
function setVoter(address _voter) external onlyOwner {
require(_voter != address(0), "zeroAddr");
address _oldVoter = address(voter);
voter = IVoter(_voter);
pairFactory = IPairFactory(voter.v2PoolFactory());
underlyingToken = IVotingEscrow(voter.votingEscrow()).token();
emit Voter(_oldVoter, _voter);
}
function getAllPair(address _user, uint _amounts, uint _offset) external view returns (pairInfo[] memory Pairs) {
require(_amounts <= MAX_PAIRS, "too many pair");
Pairs = new pairInfo[](_amounts);
uint i = _offset;
uint totPairs = pairFactory.allPairsLength();
address _pair;
for (i; i < _offset + _amounts; i++) {
// if totalPairs is reached, break.
if (i == totPairs) {
break;
}
_pair = pairFactory.allPairs(i);
Pairs[i - _offset] = _pairAddressToInfo(_pair, _user);
}
}
function getAllCLPair(address _user, uint _amounts, uint _offset) external view returns (pairInfo[] memory Pairs) {
require(_amounts <= MAX_PAIRS, "too many pair");
Pairs = new pairInfo[](_amounts);
uint i = _offset;
(, , uint totPairs) = voter.poolsCounts();
address _pair;
for (i; i < _offset + _amounts; i++) {
// if totalPairs is reached, break.
if (i == totPairs) {
break;
}
_pair = voter.v3Pools(i);
Pairs[i - _offset] = _pairAddressToCLInfo(_pair, _user);
/*if (_pair != address()) {
Pairs[i - _offset] = _pairAddressToCLInfo(_pair, _user);
}*/
}
}
function getCLPair(address _vault, address _account) external view returns (pairInfo memory _pairInfo) {
return _pairAddressToCLInfo(_vault, _account);
}
function getPair(address _pair, address _account) external view returns (pairInfo memory _pairInfo) {
return _pairAddressToInfo(_pair, _account);
}
function _pairAddressToCLInfo(address _pair, address _account) internal view returns (pairInfo memory _pairInfo) {
IPair ipair = IPair(_pair);
address token_0 = ipair.token0();
address token_1 = ipair.token1();
IGauge _gauge;
uint accountGaugeLPAmount = 0;
uint earned = 0;
uint accountGaugeLPTotalWeight = 0;
address addressGauge = voter.poolToGauge(_pair);
IVoter.GaugeState memory state = voter.getGaugeState(addressGauge);
if (state.isAlive) {
_gauge = IGauge(addressGauge);
}
if (address(_gauge) != address(0)) {
if (_account != address(0)) {
accountGaugeLPAmount = _gauge.balanceOf(_account);
earned = _gauge.earned(_account);
}
_pairInfo.gauge_total_supply = _gauge.totalSupply();
_pairInfo.gauge_total_weight = 0;
if (block.timestamp < _gauge.periodFinish()) {
_pairInfo.emissions = _gauge.rewardRate();
}
}
// Pair General Info
_pairInfo.pair_address = _pair;
_pairInfo.stable = false;
_pairInfo.clPool = _pair;
// _pairInfo.feeAmount = IV3POOL(_pair).fee();
// Token0 Info
_pairInfo.token0 = token_0;
_pairInfo.token0_decimals = IERC20MetadataUpgradeable(token_0).decimals();
_pairInfo.token0_symbol = IERC20MetadataUpgradeable(token_0).symbol();
_pairInfo.reserve0 = IERC20Upgradeable(token_0).balanceOf(_pair);
// Token1 Info
_pairInfo.token1 = token_1;
_pairInfo.token1_decimals = IERC20MetadataUpgradeable(token_1).decimals();
_pairInfo.token1_symbol = IERC20MetadataUpgradeable(token_1).symbol();
_pairInfo.reserve1 = IERC20Upgradeable(token_1).balanceOf(_pair);
// Pair's gauge Info
_pairInfo.gauge = address(_gauge);
_pairInfo.emissions_token = underlyingToken;
_pairInfo.emissions_token_decimals = IERC20MetadataUpgradeable(underlyingToken).decimals();
// external address
_pairInfo.fee = state.internalBribe;
_pairInfo.bribe = state.externalBribe;
// Account Info
_pairInfo.account_lp_balance = 0;
_pairInfo.account_token0_balance = IERC20Upgradeable(token_0).balanceOf(_account);
_pairInfo.account_token1_balance = IERC20Upgradeable(token_1).balanceOf(_account);
_pairInfo.account_gauge_balance = accountGaugeLPAmount;
_pairInfo.account_gauge_total_weight = accountGaugeLPTotalWeight;
_pairInfo.account_gauge_earned = earned;
}
function _pairAddressToInfo(address _pair, address _account) internal view returns (pairInfo memory _pairInfo) {
IPair ipair = IPair(_pair);
address token_0;
address token_1;
(token_0, token_1) = ipair.tokens();
(_pairInfo.reserve0, _pairInfo.reserve1, ) = ipair.getReserves();
IGauge _gauge;
uint accountGaugeLPAmount = 0;
uint earned = 0;
uint accountGaugeLPTotalWeight = 0;
address addressGauge = voter.poolToGauge(_pair);
IVoter.GaugeState memory state = voter.getGaugeState(addressGauge);
if (state.isAlive) {
_gauge = IGauge(addressGauge);
}
if (address(_gauge) != address(0)) {
if (_account != address(0)) {
accountGaugeLPAmount = _gauge.balanceOf(_account);
earned = _gauge.earned(_account);
//_pairInfo.tokens_info_of_account = getGaugeMaNFTsOfOwner(_account, address(_gauge));
}
_pairInfo.gauge_total_supply = _gauge.totalSupply();
if (block.timestamp < _gauge.periodFinish()) {
_pairInfo.emissions = _gauge.rewardRate();
}
}
// Pair General Info
_pairInfo.pair_address = _pair;
_pairInfo.symbol = ipair.symbol();
_pairInfo.name = ipair.name();
_pairInfo.decimals = ipair.decimals();
_pairInfo.stable = ipair.isStable();
_pairInfo.total_supply = ipair.totalSupply();
_pairInfo.clPool = address(0);
_pairInfo.feeAmount = IPairFactory(pairFactory).getFee(_pair, ipair.isStable());
// Token0 Info
_pairInfo.token0 = token_0;
_pairInfo.token0_decimals = IERC20MetadataUpgradeable(token_0).decimals();
_pairInfo.token0_symbol = IERC20MetadataUpgradeable(token_0).symbol();
_pairInfo.claimable0 = ipair.claimable0(_account);
// Token1 Info
_pairInfo.token1 = token_1;
_pairInfo.token1_decimals = IERC20MetadataUpgradeable(token_1).decimals();
_pairInfo.token1_symbol = IERC20MetadataUpgradeable(token_1).symbol();
_pairInfo.claimable1 = ipair.claimable1(_account);
// Pair's gauge Info
_pairInfo.gauge = address(_gauge);
_pairInfo.emissions_token = underlyingToken;
_pairInfo.emissions_token_decimals = IERC20MetadataUpgradeable(underlyingToken).decimals();
// external address
_pairInfo.fee = state.internalBribe;
_pairInfo.bribe = state.externalBribe;
// Account Info
_pairInfo.account_lp_balance = IERC20Upgradeable(_pair).balanceOf(_account);
_pairInfo.account_token0_balance = IERC20Upgradeable(token_0).balanceOf(_account);
_pairInfo.account_token1_balance = IERC20Upgradeable(token_1).balanceOf(_account);
_pairInfo.account_gauge_balance = accountGaugeLPAmount;
_pairInfo.account_gauge_total_weight = accountGaugeLPTotalWeight;
_pairInfo.account_gauge_earned = earned;
}
function getPairBribe(uint _amounts, uint _offset, address _pair) external view returns (pairBribeEpoch[] memory _pairEpoch) {
require(_amounts <= MAX_EPOCHS, "too many epochs");
_pairEpoch = new pairBribeEpoch[](_amounts);
address _gauge = voter.poolToGauge(_pair);
IVoter.GaugeState memory state = voter.getGaugeState(_gauge);
IBribe bribe = IBribe(state.externalBribe);
// check bribe and checkpoints exists
if (address(0) == address(bribe)) {
return _pairEpoch;
}
// scan bribes
// get latest balance and epoch start for bribes
uint _epochStartTimestamp = bribe.firstBribeTimestamp();
// if 0 then no bribe created so far
if (_epochStartTimestamp == 0) {
return _pairEpoch;
}
uint _supply;
uint i = _offset;
for (i; i < _offset + _amounts; i++) {
_supply = bribe.totalSupplyAt(_epochStartTimestamp);
_pairEpoch[i - _offset].epochTimestamp = _epochStartTimestamp;
_pairEpoch[i - _offset].pair = _pair;
_pairEpoch[i - _offset].totalVotes = _supply;
_pairEpoch[i - _offset].bribes = _bribe(_epochStartTimestamp, address(bribe));
_epochStartTimestamp += WEEK;
}
}
function _bribe(uint _ts, address _br) internal view returns (tokenBribe[] memory _tb) {
IBribe _wb = IBribe(_br);
address[] memory rewardTokens = _wb.getRewardTokens();
_tb = new tokenBribe[](rewardTokens.length);
uint k;
uint _rewPerEpoch;
IERC20MetadataUpgradeable _t;
for (k = 0; k < rewardTokens.length; k++) {
_t = IERC20MetadataUpgradeable(rewardTokens[k]);
if (address(_t) != address(0x0)) {
(, uint256 rewardsPerEpoch, ) = _wb.rewardData(address(_t), _ts);
_rewPerEpoch = rewardsPerEpoch;
if (_rewPerEpoch > 0) {
_tb[k].token = address(_t);
_tb[k].symbol = _t.symbol();
_tb[k].decimals = _t.decimals();
_tb[k].amount = _rewPerEpoch;
} else {
_tb[k].token = address(_t);
_tb[k].symbol = _t.symbol();
_tb[k].decimals = _t.decimals();
_tb[k].amount = 0;
}
} else {
_tb[k].token = address(_t);
_tb[k].symbol = "0x";
_tb[k].decimals = 0;
_tb[k].amount = 0;
}
}
}
function left(address _pair, address _token) external view returns (uint256 _rewPerEpoch) {
address _gauge = voter.poolToGauge(_pair);
IVoter.GaugeState memory state = voter.getGaugeState(_gauge);
IBribe bribe = IBribe(state.internalBribe);
uint256 _ts = bribe.getEpochStart();
(, uint256 rewardsPerEpoch, ) = bribe.rewardData(_token, _ts);
_rewPerEpoch = rewardsPerEpoch;
}
}
@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSetUpgradeable {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
@openzeppelin/contracts/utils/math/SafeMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
@openzeppelin/contracts/utils/Counters.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
pragma solidity ^0.8.0;
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
*/
library Counters {
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
unchecked {
counter._value += 1;
}
}
function decrement(Counter storage counter) internal {
uint256 value = counter._value;
require(value > 0, "Counter: decrement overflow");
unchecked {
counter._value = value - 1;
}
}
function reset(Counter storage counter) internal {
counter._value = 0;
}
}
contracts/lute/SingelTokenVirtualRewarderUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {ISingelTokenVirtualRewarder} from "./interfaces/ISingelTokenVirtualRewarder.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {VirtualRewarderCheckpoints} from "./libraries/VirtualRewarderCheckpoints.sol";
import {UpgradeCall} from "../integration/UpgradeCall.sol";
/**
* @title Single Token Virtual Rewarder Upgradeable
* @dev An upgradeable contract for managing token rewards based on virtual balances and epochs. It supports functionalities
* like deposits, withdrawals, and reward calculations based on checkpoints.
*/
contract SingelTokenVirtualRewarderUpgradeable is ISingelTokenVirtualRewarder, Initializable, UpgradeCall {
/**
* @title Struct for managing token information within a virtual reward system
* @notice Holds all pertinent data related to individual tokens within the reward system.
* @dev The structure stores balances, checkpoint indices, and a mapping of balance checkpoints.
*/
struct TokenInfo {
uint256 balance; // Current balance of the token
uint256 checkpointLastIndex; // Index of the last checkpoint for the token
uint256 lastEarnEpoch; // The last epoch during which rewards were calculated for the token
mapping(uint256 index => VirtualRewarderCheckpoints.Checkpoint) balanceCheckpoints; // Mapping of index to balance checkpoints
}
/**
* @notice Address of the strategy contract that interacts with this reward system
* @dev This should be set to the address of the strategy managing the tokens and their rewards.
*/
address public override strategy;
/**
* @notice Total supply of all tokens managed by the reward system
* @dev This total supply is used in reward calculations across different epochs.
*/
uint256 public override totalSupply;
/**
* @notice Index of the last checkpoint for the total supply
* @dev Used to track changes in total supply at each checkpoint.
*/
uint256 public totalSupplyCheckpointLastIndex;
/**
* @notice Mapping of total supply checkpoints
* @dev This stores checkpoints of the total supply which are referenced in reward calculations.
*/
mapping(uint256 index => VirtualRewarderCheckpoints.Checkpoint) public totalSupplyCheckpoints;
/**
* @notice Mapping from token ID to its associated TokenInfo
* @dev Keeps track of all relevant token information, including balances and checkpoints.
*/
mapping(uint256 tokenId => TokenInfo tokenInfo) public tokensInfo;
/**
* @notice Mapping from epoch to the total rewards allocated for that epoch
* @dev Used to determine the amount of rewards available per epoch, which influences reward calculations.
*/
mapping(uint256 epoch => uint256 rewards) public rewardsPerEpoch;
/**
* @notice Constant defining the length of a week in seconds
* @dev Used for time-related calculations, particularly in determining epoch boundaries.
*/
uint256 internal constant _WEEK = 86400 * 7;
/**
* @dev Custom error for unauthorized access attempts
* @notice Thrown when an operation is attempted by an unauthorized address, typically checked against the strategy.
*/
error AccessDenied();
/**
* @dev Custom error indicating operation involving zero amount which is not permitted
* @notice Used primarily in deposit, withdrawal, and reward distribution to prevent erroneous zero value transactions.
*/
error ZeroAmount();
error AddressZero();
/**
* @dev Modifier to restrict function calls to the strategy address
* @notice Ensures that only the designated strategy can call certain functions.
*/
modifier onlyStrategy() {
if (msg.sender != strategy) {
revert AccessDenied();
}
_;
}
/**
* @dev Constructor that disables initialization on implementation.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract with necessary governance and operational addresses
* @dev Sets up operational aspects of the contract. This function can only be called once.
*
* @param strategy_ The strategy address that will interact with this contract
*/
function initialize(address strategy_) external override initializer {
_checkAddressZero(strategy_);
strategy = strategy_;
}
/**
* @notice Deposits a specific amount of tokens for a given tokenId
* @dev This function updates the token's balance and total supply and writes a new checkpoint.
*
* @param tokenId_ The ID of the token to deposit
* @param amount_ The amount of tokens to deposit
*/
function deposit(uint256 tokenId_, uint256 amount_) external onlyStrategy {
if (amount_ == 0) {
revert ZeroAmount();
}
TokenInfo storage info = tokensInfo[tokenId_];
info.balance += amount_;
totalSupply += amount_;
uint256 currentEpoch = _currentEpoch();
_writeCheckpoints(info, currentEpoch);
emit Deposit(tokenId_, amount_, currentEpoch);
}
/**
* @notice Withdraws a specific amount of tokens for a given tokenId
* @dev This function updates the token's balance and total supply and writes a new checkpoint.
*
* @param tokenId_ The ID of the token from which to withdraw
* @param amount_ The amount of tokens to withdraw
*/
function withdraw(uint256 tokenId_, uint256 amount_) external onlyStrategy {
TokenInfo storage info = tokensInfo[tokenId_];
if (info.balance == 0 || amount_ == 0) {
revert ZeroAmount();
}
info.balance -= amount_;
totalSupply -= amount_;
uint256 currentEpoch = _currentEpoch();
_writeCheckpoints(info, currentEpoch);
emit Withdraw(tokenId_, amount_, currentEpoch);
}
/**
* @notice Harvests rewards for a specific tokenId
* @dev Calculates the available rewards for the token and updates the last earned epoch.
*
* IMPORTANT: If the reward was issued after the harvest summon in an epoch,
* you will not be able to claim it. Wait for the distribution of rewards for the past era
*
* @param tokenId_ The ID of the token for which to harvest rewards
* @return reward The amount of rewards harvested
*/
function harvest(uint256 tokenId_) external onlyStrategy returns (uint256 reward) {
reward = _calculateAvailableRewardsAmount(tokenId_);
uint256 currentEpoch = _currentEpoch();
tokensInfo[tokenId_].lastEarnEpoch = currentEpoch;
emit Harvest(tokenId_, reward, currentEpoch);
return reward;
}
/**
* @notice Notifies the contract of a new reward amount to be distributed in the current epoch
* @dev Updates the rewards for the current epoch and emits a notification event.
*
* @param amount_ The amount of rewards to distribute
*/
function notifyRewardAmount(uint256 amount_) external onlyStrategy {
uint256 currentEpoch = _currentEpoch();
rewardsPerEpoch[currentEpoch] += amount_;
emit NotifyReward(amount_, currentEpoch);
}
/**
* @notice Calculates the available rewards amount for a given tokenId
*
* @param tokenId_ The ID of the token to calculate rewards for
* @return reward The calculated reward amount
*/
function calculateAvailableRewardsAmount(uint256 tokenId_) external view returns (uint256 reward) {
return _calculateAvailableRewardsAmount(tokenId_);
}
/**
* @notice Provides the current balance of a specific tokenId
*
* @param tokenId_ The ID of the token to check
* @return The current balance of the token
*/
function balanceOf(uint256 tokenId_) external view returns (uint256) {
return tokensInfo[tokenId_].balance;
}
/**
* @notice Provides the balance of a specific tokenId at a given timestamp
*
* @param tokenId_ The ID of the token to check
* @param timestamp_ The specific timestamp to check the balance at
* @return The balance of the token at the given timestamp
*/
function balanceOfAt(uint256 tokenId_, uint256 timestamp_) external view returns (uint256) {
return
VirtualRewarderCheckpoints.getAmount(
tokensInfo[tokenId_].balanceCheckpoints,
tokensInfo[tokenId_].checkpointLastIndex,
timestamp_
);
}
/**
* @notice Provides the total supply of tokens at a given timestamp
*
* @param timestamp_ The timestamp to check the total supply at
* @return The total supply of tokens at the specified timestamp
*/
function totalSupplyAt(uint256 timestamp_) external view returns (uint256) {
return VirtualRewarderCheckpoints.getAmount(totalSupplyCheckpoints, totalSupplyCheckpointLastIndex, timestamp_);
}
/**
* @notice Returns the checkpoint data for a specific token and index
*
* @param tokenId_ The ID of the token to check
* @param index The index of the checkpoint to retrieve
* @return A checkpoint struct containing the timestamp and amount at that index
*/
function balanceCheckpoints(uint256 tokenId_, uint256 index) external view returns (VirtualRewarderCheckpoints.Checkpoint memory) {
return tokensInfo[tokenId_].balanceCheckpoints[index];
}
/**
* @dev Writes checkpoints for token balance and total supply at a given epoch.
* @notice This function updates both the token's individual balance checkpoint and the total supply checkpoint.
*
* @param info_ The storage reference to the token's information which includes balance and checkpoint index.
* @param epoch_ The epoch for which the checkpoint is being written.
*/
function _writeCheckpoints(TokenInfo storage info_, uint256 epoch_) internal {
info_.checkpointLastIndex = VirtualRewarderCheckpoints.writeCheckpoint(
info_.balanceCheckpoints,
info_.checkpointLastIndex,
epoch_,
info_.balance
);
totalSupplyCheckpointLastIndex = VirtualRewarderCheckpoints.writeCheckpoint(
totalSupplyCheckpoints,
totalSupplyCheckpointLastIndex,
epoch_,
totalSupply
);
}
/**
* @notice This function accumulates rewards over each epoch since last claimed to present.
* @dev Calculates the total available rewards for a given tokenId since the last earned epoch.
*
* @param tokenId_ The identifier of the token for which rewards are being calculated.
* @return reward The total accumulated reward since the last claim.
*/
function _calculateAvailableRewardsAmount(uint256 tokenId_) internal view returns (uint256 reward) {
uint256 checkpointLastIndex = tokensInfo[tokenId_].checkpointLastIndex;
if (checkpointLastIndex == 0) {
return 0;
}
uint256 startEpoch = tokensInfo[tokenId_].lastEarnEpoch;
uint256 index = startEpoch == 0
? 1
: VirtualRewarderCheckpoints.getCheckpointIndex(
tokensInfo[tokenId_].balanceCheckpoints,
tokensInfo[tokenId_].checkpointLastIndex,
startEpoch
);
uint256 epochTimestamp = tokensInfo[tokenId_].balanceCheckpoints[index].timestamp;
if (epochTimestamp > startEpoch) {
startEpoch = epochTimestamp;
}
uint256 currentEpoch = _currentEpoch();
uint256 notHarvestedEpochCount = (currentEpoch - startEpoch) / _WEEK;
for (uint256 i; i < notHarvestedEpochCount; ) {
reward += _calculateRewardPerEpoch(tokenId_, startEpoch);
startEpoch += _WEEK;
unchecked {
i++;
}
}
}
/**
* @notice This method uses the reward per epoch and the token's proportion of the total supply to determine the reward amount.
* @dev Calculates the reward for a specific tokenId for a single epoch based on the token's balance and total supply.
*
* @param tokenId_ The identifier of the token.
* @param epoch_ The epoch for which to calculate the reward.
* @return The calculated reward for the epoch.
*/
function _calculateRewardPerEpoch(uint256 tokenId_, uint256 epoch_) internal view returns (uint256) {
uint256 balance = VirtualRewarderCheckpoints.getAmount(
tokensInfo[tokenId_].balanceCheckpoints,
tokensInfo[tokenId_].checkpointLastIndex,
epoch_
);
uint256 supply = VirtualRewarderCheckpoints.getAmount(totalSupplyCheckpoints, totalSupplyCheckpointLastIndex, epoch_);
if (supply == 0) {
return 0;
}
return (balance * rewardsPerEpoch[epoch_ + _WEEK]) / supply;
}
/**
* @notice This function return current epoch
* @dev Retrieves the current epoch
*
* @return The current epoch
*/
function _currentEpoch() internal view returns (uint256) {
return _roundToEpoch(block.timestamp);
}
/**
* @notice This function is used to align timestamps with epoch boundaries.
* @dev Rounds down the timestamp to the start of the epoch.
*
* @param timestamp_ The timestamp to round down.
* @return The timestamp rounded down to the nearest epoch start.
*/
function _roundToEpoch(uint256 timestamp_) internal pure returns (uint256) {
return (timestamp_ / _WEEK) * _WEEK;
}
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure {
if (addr_ == address(0)) {
revert AddressZero();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/lute/SingelTokenBuybackUpgradeable.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IRouterV2} from "../dexV2/interfaces/IRouterV2.sol";
import {IRouterV2PathProvider} from "./interfaces/IRouterV2PathProvider.sol";
import {ISingelTokenBuyback} from "./interfaces/ISingelTokenBuyback.sol";
/**
* @title Single Token Buyback Upgradeable Contract
* @notice Implements token buyback functionality using DEX V2 Router.
* @dev This contract uses an upgradeable pattern along with the SafeERC20 library for token interactions.
*/
abstract contract SingelTokenBuybackUpgradeable is ISingelTokenBuyback, Initializable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @dev Emitted when the input token for a buyback operation is the same as the target token.
*/
error IncorrectInputToken();
/**
* @dev Emitted when the slippage specified for a buyback exceeds the maximum allowable limit.
*/
error IncorrectSlippage();
/**
* @dev Emitted when attempting a buyback with an empty balance.
*/
error ZeroBalance();
/**
* @dev Emitted when the input routes provided for a buyback are invalid or do not conform to expected standards
*/
error InvalidInputRoutes();
/**
* @dev Emitted when no viable route is found for the buyback operation.
*/
error RouteNotFound();
/**
* @dev Emitted when a function argument is expected to be a valid address but receives a zero address.
*/
error ZeroAddress();
/**
* @notice Maximum slippage allowed for buyback operations, represented in basis points.
* @dev Slippage is capped at 400 basis points (4%).
*/
uint256 public constant MAX_SLIPPAGE = 400;
/**
* @notice Precision used for representing slippage percentages.
* @dev Slippage calculations are based on a granularity of 10,000 to represent 100%.
*/
uint256 public constant SLIPPAGE_PRECISION = 10_000;
/**
*
* @notice Address of the Router V2 Path Provider used for fetching and calculating optimal token swap routes.
* @dev This address is utilized to access routing functionality for executing token buybacks.
*/
address public override routerV2PathProvider;
/**
* @notice Ensures the slippage value is within the acceptable range.
* @param slippage_ The slippage value to check.
* @dev Reverts with IncorrectSlippage if the slippage exceeds the maximum allowed.
*/
modifier onlyCorrectSlippage(uint256 slippage_) {
if (slippage_ > MAX_SLIPPAGE) {
revert IncorrectSlippage();
}
_;
}
/**
* @notice Ensures the provided token is not the target buyback token.
* @param token_ The token address to check.
* @dev Reverts with IncorrectInputToken if the token address matches the buyback target token.
*/
modifier onlyCorrectInputToken(address token_) {
_checkAddressZero(token_);
if (token_ == _getBuybackTargetToken()) {
revert IncorrectInputToken();
}
_;
}
/**
* @notice Initializes the buyback contract with the address of the router V2 path provider.
* @param routerV2PathProvider_ The address of the router V2 path provider to be set.
* @dev This function should be called from the contract's initializer function.
*/
function __SingelTokenBuyback__init(address routerV2PathProvider_) internal onlyInitializing {
_checkAddressZero(routerV2PathProvider_);
routerV2PathProvider = routerV2PathProvider_;
}
/**
* @notice Buys back tokens by swapping specified input tokens for a target token via a DEX
* @dev Executes a token swap using the optimal route found via Router V2 Path Provider. Ensures input token is not the target token and validates slippage.
*
* @param inputToken_ The ERC20 token to swap from.
* @param inputRouters_ Array of routes to potentially use for the swap.
* @param slippage_ The maximum allowed slippage for the swap, in basis points.
* @param deadline_ Unix timestamp after which the transaction will revert.
*/
function buybackTokenByV2(
address inputToken_,
IRouterV2.route[] calldata inputRouters_,
uint256 slippage_,
uint256 deadline_
) external virtual override onlyCorrectInputToken(inputToken_) onlyCorrectSlippage(slippage_) returns (uint256 outputAmount) {
_checkBuybackSwapPermissions();
IERC20Upgradeable inputTokenCache = IERC20Upgradeable(inputToken_);
uint256 amountIn = inputTokenCache.balanceOf(address(this));
if (amountIn == 0) {
revert ZeroBalance();
}
address targetToken = _getBuybackTargetToken();
IRouterV2PathProvider routerV2PathProviderCache = IRouterV2PathProvider(routerV2PathProvider);
(IRouterV2.route[] memory optimalRoute, ) = routerV2PathProviderCache.getOptimalTokenToTokenRoute(
inputToken_,
targetToken,
amountIn
);
uint256 amountOutQuote;
if (optimalRoute.length > 0) {
amountOutQuote = routerV2PathProviderCache.getAmountOutQuote(amountIn, optimalRoute);
}
if (inputRouters_.length > 1) {
if (inputRouters_[0].from != inputToken_ || inputRouters_[inputRouters_.length - 1].to != targetToken) {
revert InvalidInputRoutes();
}
if (!routerV2PathProviderCache.isValidInputRoutes(inputRouters_)) {
revert InvalidInputRoutes();
}
uint256 amountOutQuoteInputRouters = routerV2PathProviderCache.getAmountOutQuote(amountIn, inputRouters_);
if (amountOutQuoteInputRouters > amountOutQuote) {
optimalRoute = inputRouters_;
amountOutQuote = amountOutQuoteInputRouters;
}
}
amountOutQuote = amountOutQuote - (amountOutQuote * slippage_) / SLIPPAGE_PRECISION;
if (amountOutQuote == 0) {
revert RouteNotFound();
}
IRouterV2 router = IRouterV2(routerV2PathProviderCache.router());
inputTokenCache.forceApprove(address(router), amountIn);
uint256 balanceBefore = IERC20Upgradeable(targetToken).balanceOf(address(this));
uint256[] memory amountsOut = router.swapExactTokensForTokens(amountIn, amountOutQuote, optimalRoute, address(this), deadline_);
uint256 amountOut = amountsOut[amountsOut.length - 1];
assert(IERC20Upgradeable(targetToken).balanceOf(address(this)) - balanceBefore == amountOut);
assert(amountOut > 0);
emit BuybackTokenByV2(msg.sender, inputToken_, targetToken, optimalRoute, amountIn, amountOut);
return amountOut;
}
/**
* @notice Retrieves the target token for buybacks.
* @dev Provides an abstraction layer over internal details, potentially allowing for dynamic updates in the future.
* @return The address of the token targeted for buyback operations.
*/
function getBuybackTargetToken() external view returns (address) {
return _getBuybackTargetToken();
}
/**
* @dev Internal function to enforce permissions or rules before allowing a buyback swap to proceed.
*/
function _checkBuybackSwapPermissions() internal view virtual;
/**
* @dev Internal helper to fetch the target token for buybacks.
* @return The address of the buyback target token.
*/
function _getBuybackTargetToken() internal view virtual returns (address);
/**
* @dev Checked provided address on zero value, throw AddressZero error in case when addr_ is zero
*
* @param addr_ The address which will checked on zero
*/
function _checkAddressZero(address addr_) internal pure virtual {
if (addr_ == address(0)) {
revert ZeroAddress();
}
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
contracts/integration/MerklGaugeMiddleman.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IDistributionCreator, DistributionParameters} from "./interfaces/IDistributionCreator.sol";
import {IMerklGaugeMiddleman} from "./interfaces/IMerklGaugeMiddleman.sol";
import {IPairIntegrationInfo} from "./interfaces/IPairIntegrationInfo.sol";
/**
* @title Merkl Gauge Middleman Contract
* @dev This contract acts as a middleman between Gauges and the DistributionCreator,
* facilitating the reward distribution process. It allows setting up distribution parameters
* for each gauge, adjusting token allowance, and notifying about rewards.
*
* This version is a modified implementation based on the MerklGaugeMiddleman contract from Angle Protocol.
* See original implementation at: https://github.com/AngleProtocol/merkl-contracts/blob/main/contracts/middleman/MerklGaugeMiddleman.sol
*
* The contract uses OpenZeppelin's Ownable for ownership management and SafeERC20 for safe ERC20 interactions.
*/
contract MerklGaugeMiddleman is IMerklGaugeMiddleman, Ownable {
using SafeERC20 for IERC20;
// Mapping of each gauge to its reward distribution parameters
mapping(address => DistributionParameters) public gaugeParams;
// token interface
IERC20 public token;
// Distribution creator contract interface
IDistributionCreator public merklDistributionCreator;
error AddressZero();
constructor(address token_, address merklDistributionCreator_) {
if (token_ == address(0) || merklDistributionCreator_ == address(0)) {
revert AddressZero();
}
token = IERC20(token_);
merklDistributionCreator = IDistributionCreator(merklDistributionCreator_);
merklDistributionCreator.acceptConditions();
IERC20(token_).safeIncreaseAllowance(merklDistributionCreator_, type(uint256).max);
}
// ============================= EXTERNAL FUNCTIONS ============================
/// @notice Restores the allowance for the token to the `DistributionCreator` contract
/// Depending on the token implementation, not needed for Lute implementations
function setLuteAllowance() external {
IERC20 luteCache = token;
address creator = address(merklDistributionCreator);
uint256 currentAllowance = luteCache.allowance(address(this), creator);
if (currentAllowance < type(uint256).max) luteCache.safeIncreaseAllowance(creator, type(uint256).max - currentAllowance);
}
/**
* @dev Sets the reward distribution parameters for a specific gauge. Only callable by the contract owner.
* Ensures the gauge and reward token addresses are valid and that the reward token is whitelisted.
*
* @param gauge_ Address of the gauge for which to set the parameters
* @param params_ DistributionParameters struct containing the reward distribution settings
*/
function setGauge(address gauge_, DistributionParameters memory params_) external onlyOwner {
IDistributionCreator creator = merklDistributionCreator;
if (gauge_ == address(0) || params_.rewardToken != address(token) || creator.rewardTokenMinAmounts(params_.rewardToken) == 0)
revert InvalidParams();
gaugeParams[gauge_] = params_;
emit GaugeSet(gauge_);
}
/**
* @dev Notifies the DistributionCreator about the reward for a specific gauge. Can be called by any contract.
* It's an override of the IMerklGaugeMiddleman interface.
*
* @param gauge_ Address of the gauge to notify
* @param amount_ Amount of the reward
*/
function notifyReward(address gauge_, uint256 amount_) external virtual override {
_notifyReward(gauge_, amount_);
}
/**
* @dev Transfers tokens from the caller and notifies the DistributionCreator about the reward.
* This function allows combining the transfer and notification into a single transaction.
*
* @param gauge_ Address of the gauge to notify
* @param amount_ Amount of tokens to transfer and notify about
*/
function notifyRewardWithTransfer(address gauge_, uint256 amount_) external virtual override {
token.safeTransferFrom(msg.sender, address(this), amount_);
_notifyReward(gauge_, amount_);
}
/**
* @dev Internal function to handle the notification logic. Validates the gauge parameters and amount,
* then either creates a distribution or refunds the tokens if the amount is below the minimum threshold.
*
* @param gauge_ Address of the gauge to notify
* @param amount_ Amount of tokens to use for the distribution
*/
function _notifyReward(address gauge_, uint256 amount_) internal {
DistributionParameters memory params = gaugeParams[gauge_];
if (params.uniV3Pool == address(0)) revert InvalidParams();
IERC20 tokenChache = token;
if (amount_ == 0) amount_ = tokenChache.balanceOf(address(this));
if (amount_ > 0) {
params.epochStart = uint32(block.timestamp);
params.amount = amount_;
IDistributionCreator creatorCache = merklDistributionCreator;
if (amount_ > creatorCache.rewardTokenMinAmounts(address(tokenChache)) * params.numEpoch) {
uint256 distributionAmount = creatorCache.createDistribution(params);
emit CreateDistribution(msg.sender, gauge_, amount_, distributionAmount);
} else {
tokenChache.safeTransfer(msg.sender, amount_);
}
}
}
}
@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol)
pragma solidity ^0.8.0;
import "../ERC721.sol";
import "./IERC721Enumerable.sol";
/**
* @dev This implements an optional extension of {ERC721} defined in the EIP that adds
* enumerability of all the token ids in the contract as well as all token ids owned by each
* account.
*/
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
// Mapping from owner to list of owned token IDs
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
// Mapping from token ID to index of the owner tokens list
mapping(uint256 => uint256) private _ownedTokensIndex;
// Array with all token ids, used for enumeration
uint256[] private _allTokens;
// Mapping from token id to position in the allTokens array
mapping(uint256 => uint256) private _allTokensIndex;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
/**
* @dev See {IERC721Enumerable-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _allTokens.length;
}
/**
* @dev See {IERC721Enumerable-tokenByIndex}.
*/
function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
/**
* @dev See {ERC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
if (batchSize > 1) {
// Will only trigger during construction. Batch transferring (minting) is not available afterwards.
revert("ERC721Enumerable: consecutive transfers not supported");
}
uint256 tokenId = firstTokenId;
if (from == address(0)) {
_addTokenToAllTokensEnumeration(tokenId);
} else if (from != to) {
_removeTokenFromOwnerEnumeration(from, tokenId);
}
if (to == address(0)) {
_removeTokenFromAllTokensEnumeration(tokenId);
} else if (to != from) {
_addTokenToOwnerEnumeration(to, tokenId);
}
}
/**
* @dev Private function to add a token to this extension's ownership-tracking data structures.
* @param to address representing the new owner of the given token ID
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
*/
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = ERC721.balanceOf(to);
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;
}
/**
* @dev Private function to add a token to this extension's token tracking data structures.
* @param tokenId uint256 ID of the token to be added to the tokens list
*/
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
/**
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
* @param from address representing the previous owner of the given token ID
* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
*/
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
}
// This also deletes the contents at the last position of the array
delete _ownedTokensIndex[tokenId];
delete _ownedTokens[from][lastTokenIndex];
}
/**
* @dev Private function to remove a token from this extension's token tracking data structures.
* This has O(1) time complexity, but alters the order of the _allTokens array.
* @param tokenId uint256 ID of the token to be removed from the tokens list
*/
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
// This also deletes the contents at the last position of the array
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
}
contracts/mocks/BaseManagedNFTStrategyUpgradeableMock.sol
// SPDX-License-Identifier: MIT
pragma solidity =0.8.19;
import {BaseManagedNFTStrategyUpgradeable} from "../lute/BaseManagedNFTStrategyUpgradeable.sol";
contract BaseManagedNFTStrategyUpgradeableMock is BaseManagedNFTStrategyUpgradeable {
function initialize(address managedNFTManager_, string memory name_) external initializer {
__BaseManagedNFTStrategy__init(managedNFTManager_, name_);
}
function onAttach(uint256 tokenId, uint256 userBalance) external override {
revert("not implemented");
}
function onDettach(uint256 tokenId, uint256 userBalance) external override returns (uint256 lockedRewards) {
revert("not implemented");
}
function dettachLockWindowInfo()
external
view
returns (
bool locked,
uint256 epochStart,
uint256 lockEnd
) {}
function detachmentLockDuration() external view returns (uint256 duration) {}
}
contracts/lute/VirtualRewarderProxy.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import {ICompoundVeLUTEManagedNFTStrategyFactory} from "./interfaces/ICompoundVeLUTEManagedNFTStrategyFactory.sol";
contract VirtualRewarderProxy {
address private immutable factory;
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
constructor() {
factory = msg.sender;
}
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
function _setImplementation(address newImplementation) private {
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
fallback() external payable {
address impl = ICompoundVeLUTEManagedNFTStrategyFactory(factory).virtualRewarderImplementation();
require(impl != address(0));
//Just for etherscan compatibility
if (impl != _getImplementation() && msg.sender != (address(0))) {
_setImplementation(impl);
}
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
}
@openzeppelin/contracts/security/Pausable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
@openzeppelin/contracts/utils/introspection/IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}