Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/partners/tokenWrappers/PullTokenWrapperWithdraw.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { DistributionCreator } from "../../DistributionCreator.sol";
import { UUPSHelper } from "../../utils/UUPSHelper.sol";
import { IAccessControlManager } from "../../interfaces/IAccessControlManager.sol";
import { Errors } from "../../utils/Errors.sol";
interface IAaveToken {
function POOL() external view returns (address);
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
}
interface IAavePool {
function withdraw(address asset, uint256 amount, address to) external returns (uint256);
}
/// @title PullTokenWrapperWithdraw
/// @notice Wrapper for a reward token on Merkl so campaigns do not have to be prefunded
contract PullTokenWrapperWithdraw is UUPSHelper, ERC20Upgradeable {
using SafeERC20 for IERC20;
// ================================= VARIABLES =================================
/// @notice `AccessControlManager` contract handling access control
IAccessControlManager public accessControlManager;
// Could be put as immutable in a non upgradeable contract
address public token;
address public holder;
address public feeRecipient;
address public distributor;
address public distributionCreator;
address public pool;
address public underlying;
// ================================= MODIFIERS =================================
/// @notice Checks whether the `msg.sender` has the governor role or the guardian role
modifier onlyHolderOrGovernor() {
if (msg.sender != holder && !accessControlManager.isGovernor(msg.sender)) revert Errors.NotAllowed();
_;
}
// ================================= FUNCTIONS =================================
function initialize(
address _token,
address _distributionCreator,
address _holder,
string memory _name,
string memory _symbol
) public initializer {
__ERC20_init(string.concat(_name), string.concat(_symbol));
__UUPSUpgradeable_init();
if (_holder == address(0)) revert Errors.ZeroAddress();
IERC20(_token).balanceOf(_holder);
distributor = DistributionCreator(_distributionCreator).distributor();
accessControlManager = DistributionCreator(_distributionCreator).accessControlManager();
token = _token;
distributionCreator = _distributionCreator;
holder = _holder;
pool = IAaveToken(_token).POOL();
underlying = IAaveToken(_token).UNDERLYING_ASSET_ADDRESS();
_setFeeRecipient();
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
// During claim transactions, tokens are pulled and transferred to the `to` address
if (from == distributor || to == feeRecipient) {
IERC20(token).safeTransferFrom(holder, address(this), amount);
IAavePool(pool).withdraw(underlying, amount, to);
}
}
function _afterTokenTransfer(address, address to, uint256 amount) internal override {
// No leftover tokens can be kept except on the holder address
if (to != address(distributor) && to != holder && to != address(0)) _burn(to, amount);
}
function setHolder(address _newHolder) external onlyHolderOrGovernor {
holder = _newHolder;
}
function mint(uint256 amount) external onlyHolderOrGovernor {
_mint(holder, amount);
}
function setFeeRecipient() external {
_setFeeRecipient();
}
function _setFeeRecipient() internal {
address _feeRecipient = DistributionCreator(distributionCreator).feeRecipient();
feeRecipient = _feeRecipient;
}
function decimals() public view override returns (uint8) {
return IERC20Metadata(token).decimals();
}
/// @inheritdoc UUPSHelper
function _authorizeUpgrade(address) internal view override onlyGovernorUpgrader(accessControlManager) {}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol": {
"content": "// 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 "../../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;
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// 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));
}
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// 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/DistributionCreator.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { UUPSHelper } from "./utils/UUPSHelper.sol";
import { IAccessControlManager } from "./interfaces/IAccessControlManager.sol";
import { Errors } from "./utils/Errors.sol";
import { CampaignParameters } from "./struct/CampaignParameters.sol";
import { DistributionParameters } from "./struct/DistributionParameters.sol";
import { RewardTokenAmounts } from "./struct/RewardTokenAmounts.sol";
import { Distributor } from "./Distributor.sol";
/// @title DistributionCreator
/// @author Angle Labs, Inc.
/// @notice Manages the distribution of rewards through the Merkl system
/// @dev This contract is mostly a helper for APIs built on top of Merkl
/// @dev This contract distinguishes two types of different rewards:
/// - distributions: type of campaign for concentrated liquidity pools created before Feb 15 2024,
/// now deprecated
/// - campaigns: the more global name to describe any reward program on top of Merkl
//solhint-disable
contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable {
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CONSTANTS / VARIABLES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
uint32 public constant HOUR = 3600;
/// @notice Base for fee computation
uint256 public constant BASE_9 = 1e9;
uint256 public immutable CHAIN_ID = block.chainid;
/// @notice `AccessControlManager` contract handling access control
IAccessControlManager public accessControlManager;
/// @notice Contract distributing rewards to users
address public distributor;
/// @notice Address to which fees are forwarded
address public feeRecipient;
/// @notice Value (in base 10**9) of the fees taken when creating a campaign
uint256 public defaultFees;
/// @notice Message that needs to be acknowledged by users creating a campaign
string public message;
/// @notice Hash of the message that needs to be signed
bytes32 public messageHash;
/// @notice List of all rewards distributed in the contract on campaigns created before mid Feb 2024
/// for concentrated liquidity pools
DistributionParameters[] public distributionList;
/// @notice Maps an address to its fee rebate
mapping(address => uint256) public feeRebate;
/// @notice Maps a token to whether it is whitelisted or not. No fees are to be paid for incentives given
/// on pools with whitelisted tokens
mapping(address => uint256) public isWhitelistedToken;
/// @notice Deprecated, kept for storage compatibility
mapping(address => uint256) public _nonces;
/// @notice Maps an address to the last valid hash signed
mapping(address => bytes32) public userSignatures;
/// @notice Maps a user to whether it is whitelisted for not signing
mapping(address => uint256) public userSignatureWhitelist;
/// @notice Maps a token to the minimum amount that must be sent per epoch for a distribution to be valid
/// @dev If `rewardTokenMinAmounts[token] == 0`, then `token` cannot be used as a reward
mapping(address => uint256) public rewardTokenMinAmounts;
/// @notice List of all reward tokens that have at some point been accepted
address[] public rewardTokens;
/// @notice List of all rewards ever distributed or to be distributed in the contract
/// @dev An attacker could try to populate this list. It shouldn't be an issue as only view functions
/// iterate on it
CampaignParameters[] public campaignList;
/// @notice Maps a campaignId to the ID of the campaign in the campaign list + 1
mapping(bytes32 => uint256) internal _campaignLookup;
/// @notice Maps a campaign type to the fees for this specific campaign
mapping(uint32 => uint256) public campaignSpecificFees;
/// @notice Maps a campaignId to a potential override written
mapping(bytes32 => CampaignParameters) public campaignOverrides;
/// @notice Maps a campaignId to the block numbers at which it's been updated
mapping(bytes32 => uint256[]) public campaignOverridesTimestamp;
/// @notice Maps one address to another one to reallocate rewards for a given campaign
mapping(bytes32 => mapping(address => address)) public campaignReallocation;
/// @notice List all reallocated address for a given campaign
mapping(bytes32 => address[]) public campaignListReallocation;
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
event DistributorUpdated(address indexed _distributor);
event FeeRebateUpdated(address indexed user, uint256 userFeeRebate);
event FeeRecipientUpdated(address indexed _feeRecipient);
event FeesSet(uint256 _fees);
event CampaignOverride(bytes32 _campaignId, CampaignParameters campaign);
event CampaignReallocation(bytes32 _campaignId, address[] indexed from, address indexed to);
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);
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Checks whether the `msg.sender` has the governor role or the guardian role
modifier onlyGovernorOrGuardian() {
if (!accessControlManager.isGovernorOrGuardian(msg.sender)) revert Errors.NotGovernorOrGuardian();
_;
}
/// @notice Checks whether the `msg.sender` has the governor role or the guardian role
modifier onlyGovernor() {
if (!accessControlManager.isGovernor(msg.sender)) revert Errors.NotGovernor();
_;
}
/// @notice Checks whether an address has signed the message or not
modifier hasSigned() {
if (
userSignatureWhitelist[msg.sender] == 0 &&
userSignatures[msg.sender] != messageHash &&
userSignatureWhitelist[tx.origin] == 0 &&
userSignatures[tx.origin] != messageHash
) revert Errors.NotSigned();
_;
}
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
function initialize(
IAccessControlManager _accessControlManager,
address _distributor,
uint256 _fees
) external initializer {
if (address(_accessControlManager) == address(0) || _distributor == address(0)) revert Errors.ZeroAddress();
if (_fees >= BASE_9) revert Errors.InvalidParam();
distributor = _distributor;
accessControlManager = _accessControlManager;
defaultFees = _fees;
}
constructor() initializer {}
/// @inheritdoc UUPSHelper
function _authorizeUpgrade(address) internal view override onlyGovernorUpgrader(accessControlManager) {}
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
USER FACING FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Creates a `campaign` to incentivize a given pool for a specific period of time
/// @return The campaignId of the new campaign
/// @dev If the campaign is badly specified, it will not be handled by the campaign script and rewards may be lost
/// @dev Reward tokens sent as part of campaigns must have been whitelisted before and amounts
/// sent should be bigger than a minimum amount specific to each token
/// @dev This function reverts if the sender has not accepted the terms and conditions
function createCampaign(CampaignParameters memory newCampaign) external nonReentrant hasSigned returns (bytes32) {
return _createCampaign(newCampaign);
}
/// @notice Same as the function above but for multiple campaigns at once
/// @return List of all the campaign amounts actually deposited for each `campaign` in the `campaigns` list
function createCampaigns(
CampaignParameters[] memory campaigns
) external nonReentrant hasSigned returns (bytes32[] memory) {
uint256 campaignsLength = campaigns.length;
bytes32[] memory campaignIds = new bytes32[](campaignsLength);
for (uint256 i; i < campaignsLength; ) {
campaignIds[i] = _createCampaign(campaigns[i]);
unchecked {
++i;
}
}
return campaignIds;
}
/// @notice Allows a user to accept the conditions without signing the message
/// @dev Users may either call `acceptConditions` here or `sign` the message
function acceptConditions() external {
userSignatureWhitelist[msg.sender] = 1;
}
/// @notice Checks whether the `msg.sender`'s `signature` is compatible with the message
/// to sign and stores the signature
/// @dev If you signed the message once, and the message has not been modified, then you do not
/// need to sign again
function sign(bytes calldata signature) external {
_sign(signature);
}
/// @notice Combines signing the message and creating a campaign
function signAndCreateCampaign(
CampaignParameters memory newCampaign,
bytes calldata signature
) external returns (bytes32) {
_sign(signature);
return _createCampaign(newCampaign);
}
/// @notice Creates a `distribution` to incentivize a given pool for a specific period of time
function createDistribution(
DistributionParameters memory newDistribution
) external nonReentrant hasSigned returns (uint256 distributionAmount) {
return _createDistribution(newDistribution);
}
/// @notice Same as the function above but for multiple distributions at once
function createDistributions(
DistributionParameters[] memory distributions
) external nonReentrant hasSigned returns (uint256[] memory) {
uint256 distributionsLength = distributions.length;
uint256[] memory distributionAmounts = new uint256[](distributionsLength);
for (uint256 i; i < distributionsLength; ) {
distributionAmounts[i] = _createDistribution(distributions[i]);
unchecked {
++i;
}
}
return distributionAmounts;
}
/// @notice Overrides a campaign with new parameters
/// @dev Some overrides maybe incorrect, but their correctness cannot be checked onchain. It is up to the Merkl
/// engine to check the validity of the override. If the override is invalid, then the first campaign details
/// will still apply.
/// @dev Some fields in the new campaign parameters will be disregarded anyway (like the amount)
function overrideCampaign(bytes32 _campaignId, CampaignParameters memory newCampaign) external {
CampaignParameters memory _campaign = campaign(_campaignId);
if (
_campaign.creator != msg.sender ||
newCampaign.rewardToken != _campaign.rewardToken ||
newCampaign.amount != _campaign.amount ||
(newCampaign.startTimestamp != _campaign.startTimestamp && block.timestamp > _campaign.startTimestamp) || // Allow to update startTimestamp before campaign start
// End timestamp should be in the future
newCampaign.duration + _campaign.startTimestamp <= block.timestamp
) revert Errors.InvalidOverride();
// Take a new fee to not trick the system by creating a campaign with the smallest fee
// and then overriding it with a campaign with a bigger fee
_computeFees(newCampaign.campaignType, newCampaign.amount, newCampaign.rewardToken);
newCampaign.campaignId = _campaignId;
newCampaign.creator = msg.sender;
campaignOverrides[_campaignId] = newCampaign;
campaignOverridesTimestamp[_campaignId].push(block.timestamp);
emit CampaignOverride(_campaignId, newCampaign);
}
/// @notice Reallocates rewards of a given campaign from one address to another
/// @dev To prevent manipulations by campaign creators, this function can only be called by the
/// initial campaign creator if the `from` address has never claimed any reward on the chain
/// @dev Compute engine should also make sure when reallocating rewards that `from` claimed amount
/// is still 0 - otherwise double allocation can happen
/// @dev It is meant to be used for the case of addresses accruing rewards but unable to claim them
function reallocateCampaignRewards(bytes32 _campaignId, address[] memory froms, address to) external {
CampaignParameters memory _campaign = campaign(_campaignId);
if (_campaign.creator != msg.sender || block.timestamp < _campaign.startTimestamp + _campaign.duration)
revert Errors.InvalidOverride();
uint256 fromsLength = froms.length;
address[] memory successfullFrom = new address[](fromsLength);
uint256 count = 0;
for (uint256 i; i < fromsLength; i++) {
(uint208 amount, uint48 timestamp, ) = Distributor(distributor).claimed(froms[i], _campaign.rewardToken);
if (amount == 0 && timestamp == 0) {
successfullFrom[count] = froms[i];
campaignReallocation[_campaignId][froms[i]] = to;
campaignListReallocation[_campaignId].push(froms[i]);
count++;
}
}
assembly {
mstore(successfullFrom, count)
}
if (count == 0) revert Errors.InvalidOverride();
emit CampaignReallocation(_campaignId, successfullFrom, to);
}
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GETTERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Returns the distribution at a given index converted into a campaign
function distribution(uint256 index) external view returns (CampaignParameters memory) {
return _convertDistribution(distributionList[index]);
}
/// @notice Returns the index of a campaign in the campaign list
function campaignLookup(bytes32 _campaignId) public view returns (uint256) {
uint256 index = _campaignLookup[_campaignId];
if (index == 0) revert Errors.CampaignDoesNotExist();
return index - 1;
}
/// @notice Returns the campaign parameters of a given campaignId
/// @dev If a campaign has been overriden, this function still shows the original state of the campaign
function campaign(bytes32 _campaignId) public view returns (CampaignParameters memory) {
return campaignList[campaignLookup(_campaignId)];
}
/// @notice Returns the campaign ID for a given campaign
/// @dev The campaign ID is computed as the hash of the following parameters:
/// - `campaign.chainId`
/// - `campaign.creator`
/// - `campaign.rewardToken`
/// - `campaign.campaignType`
/// - `campaign.startTimestamp`
/// - `campaign.duration`
/// - `campaign.campaignData`
/// This prevents the creation by the same account of two campaigns with the same parameters
/// which is not a huge issue
function campaignId(CampaignParameters memory campaignData) public view returns (bytes32) {
return
bytes32(
keccak256(
abi.encodePacked(
CHAIN_ID,
campaignData.creator,
campaignData.rewardToken,
campaignData.campaignType,
campaignData.startTimestamp,
campaignData.duration,
campaignData.campaignData
)
)
);
}
/// @notice Returns the list of all the reward tokens supported as well as their minimum amounts
/// @dev Not to be queried on-chain and hence not optimized for gas consumption
function getValidRewardTokens() external view returns (RewardTokenAmounts[] memory) {
(RewardTokenAmounts[] memory validRewardTokens, ) = _getValidRewardTokens(0, type(uint32).max);
return validRewardTokens;
}
/// @dev Not to be queried on-chain and hence not optimized for gas consumption
function getValidRewardTokens(
uint32 skip,
uint32 first
) external view returns (RewardTokenAmounts[] memory, uint256) {
return _getValidRewardTokens(skip, first);
}
/// @notice Gets all the campaigns which were live at some point between `start` and `end` timestamp
/// @param skip Disregard distibutions with a global index lower than `skip`
/// @param first Limit the length of the returned array to `first`
/// @return searchCampaigns Eligible campaigns
/// @return lastIndexCampaign Index of the last campaign assessed in the list of all campaigns
/// @dev For pagniation purpose, in case of out of gas, you can call back the same function but with `skip` set to `lastIndexCampaign`
/// @dev Not to be queried on-chain and hence not optimized for gas consumption
function getCampaignsBetween(
uint32 start,
uint32 end,
uint32 skip,
uint32 first
) external view returns (CampaignParameters[] memory, uint256 lastIndexCampaign) {
return _getCampaignsBetween(start, end, skip, first);
}
function getCampaignOverridesTimestamp(bytes32 _campaignId) external view returns (uint256[] memory) {
return campaignOverridesTimestamp[_campaignId];
}
function getCampaignListReallocation(bytes32 _campaignId) external view returns (address[] memory) {
return campaignListReallocation[_campaignId];
}
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GOVERNANCE FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Sets a new `distributor` to which rewards should be distributed
function setNewDistributor(address _distributor) external onlyGovernor {
if (_distributor == address(0)) revert Errors.InvalidParam();
distributor = _distributor;
emit DistributorUpdated(_distributor);
}
/// @notice Sets the defaultFees on deposit
function setFees(uint256 _defaultFees) external onlyGovernor {
if (_defaultFees >= BASE_9) revert Errors.InvalidParam();
defaultFees = _defaultFees;
emit FeesSet(_defaultFees);
}
/// @notice Recovers fees accrued on the contract for a list of `tokens`
function recoverFees(IERC20[] calldata tokens, address to) external onlyGovernor {
uint256 tokensLength = tokens.length;
for (uint256 i; i < tokensLength; ) {
tokens[i].safeTransfer(to, tokens[i].balanceOf(address(this)));
unchecked {
++i;
}
}
}
/// @notice Sets a new address to receive fees
function setFeeRecipient(address _feeRecipient) external onlyGovernor {
feeRecipient = _feeRecipient;
emit FeeRecipientUpdated(_feeRecipient);
}
/// @notice Sets the message that needs to be signed by users before posting rewards
function setMessage(string memory _message) external onlyGovernor {
message = _message;
bytes32 _messageHash = ECDSA.toEthSignedMessageHash(bytes(_message));
messageHash = _messageHash;
emit MessageUpdated(_messageHash);
}
/// @notice Sets the fees specific for a campaign
/// @dev To waive the fees for a campaign, set its fees to 1
function setCampaignFees(uint32 campaignType, uint256 _fees) external onlyGovernorOrGuardian {
if (_fees >= BASE_9) revert Errors.InvalidParam();
campaignSpecificFees[campaignType] = _fees;
emit CampaignSpecificFeesSet(campaignType, _fees);
}
/// @notice Toggles the fee whitelist for `token`
function toggleTokenWhitelist(address token) external onlyGovernorOrGuardian {
uint256 toggleStatus = 1 - isWhitelistedToken[token];
isWhitelistedToken[token] = toggleStatus;
emit TokenWhitelistToggled(token, toggleStatus);
}
/// @notice Sets fee rebates for a given user
function setUserFeeRebate(address user, uint256 userFeeRebate) external onlyGovernorOrGuardian {
feeRebate[user] = userFeeRebate;
emit FeeRebateUpdated(user, userFeeRebate);
}
/// @notice Sets the minimum amounts per distribution epoch for different reward tokens
function setRewardTokenMinAmounts(
address[] calldata tokens,
uint256[] calldata amounts
) external onlyGovernorOrGuardian {
uint256 tokensLength = tokens.length;
if (tokensLength != amounts.length) revert Errors.InvalidLengths();
for (uint256 i; i < tokensLength; ++i) {
uint256 amount = amounts[i];
// Basic logic check to make sure there are no duplicates in the `rewardTokens` table. If a token is
// removed then re-added, it will appear as a duplicate in the list
if (amount != 0 && rewardTokenMinAmounts[tokens[i]] == 0) rewardTokens.push(tokens[i]);
rewardTokenMinAmounts[tokens[i]] = amount;
emit RewardTokenMinimumAmountUpdated(tokens[i], amount);
}
}
/// @notice Toggles the whitelist status for `user` when it comes to signing messages before depositing rewards.
function toggleSigningWhitelist(address user) external onlyGovernorOrGuardian {
uint256 whitelistStatus = 1 - userSignatureWhitelist[user];
userSignatureWhitelist[user] = whitelistStatus;
emit UserSigningWhitelistToggled(user, whitelistStatus);
}
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
INTERNAL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Internal version of `createCampaign`
function _createCampaign(CampaignParameters memory newCampaign) internal returns (bytes32) {
uint256 rewardTokenMinAmount = rewardTokenMinAmounts[newCampaign.rewardToken];
// if the campaign doesn't last at least one hour
if (newCampaign.duration < HOUR) revert Errors.CampaignDurationBelowHour();
// if the reward token is not whitelisted as an incentive token
if (rewardTokenMinAmount == 0) revert Errors.CampaignRewardTokenNotWhitelisted();
// if the amount distributed is too small with respect to what is allowed
if ((newCampaign.amount * HOUR) / newCampaign.duration < rewardTokenMinAmount)
revert Errors.CampaignRewardTooLow();
if (newCampaign.creator == address(0)) newCampaign.creator = msg.sender;
// Computing fees: these are waived for whitelisted addresses and if there is a whitelisted token in a pool
uint256 campaignAmountMinusFees = _computeFees(
newCampaign.campaignType,
newCampaign.amount,
newCampaign.rewardToken
);
IERC20(newCampaign.rewardToken).safeTransferFrom(msg.sender, distributor, campaignAmountMinusFees);
newCampaign.amount = campaignAmountMinusFees;
newCampaign.campaignId = campaignId(newCampaign);
if (_campaignLookup[newCampaign.campaignId] != 0) revert Errors.CampaignAlreadyExists();
_campaignLookup[newCampaign.campaignId] = campaignList.length + 1;
campaignList.push(newCampaign);
emit NewCampaign(newCampaign);
return newCampaign.campaignId;
}
/// @notice Creates a distribution from a deprecated distribution type
function _createDistribution(DistributionParameters memory newDistribution) internal returns (uint256) {
_createCampaign(_convertDistribution(newDistribution));
// Not gas efficient but deprecated
return campaignList[campaignList.length - 1].amount;
}
/// @notice Converts the deprecated distribution type into a campaign
function _convertDistribution(
DistributionParameters memory distributionToConvert
) internal view returns (CampaignParameters memory) {
uint256 wrapperLength = distributionToConvert.wrapperTypes.length;
address[] memory whitelist = new address[](wrapperLength);
address[] memory blacklist = new address[](wrapperLength);
uint256 whitelistLength;
uint256 blacklistLength;
for (uint256 k = 0; k < wrapperLength; k++) {
if (distributionToConvert.wrapperTypes[k] == 0) {
whitelist[whitelistLength] = (distributionToConvert.positionWrappers[k]);
whitelistLength += 1;
}
if (distributionToConvert.wrapperTypes[k] == 3) {
blacklist[blacklistLength] = (distributionToConvert.positionWrappers[k]);
blacklistLength += 1;
}
}
assembly {
mstore(whitelist, whitelistLength)
mstore(blacklist, blacklistLength)
}
return
CampaignParameters({
campaignId: distributionToConvert.rewardId,
creator: msg.sender,
rewardToken: distributionToConvert.rewardToken,
amount: distributionToConvert.amount,
campaignType: 2,
startTimestamp: distributionToConvert.epochStart,
duration: distributionToConvert.numEpoch * HOUR,
campaignData: abi.encode(
distributionToConvert.uniV3Pool,
distributionToConvert.propFees, // eg. 6000
distributionToConvert.propToken0, // eg. 3000
distributionToConvert.propToken1, // eg. 1000
distributionToConvert.isOutOfRangeIncentivized, // eg. 0
distributionToConvert.boostingAddress, // eg. NULL_ADDRESS
distributionToConvert.boostedReward, // eg. 0
whitelist, // eg. []
blacklist, // eg. []
"0x"
)
});
}
/// @notice Computes the fees to be taken on a campaign and transfers them to the fee recipient
function _computeFees(
uint32 campaignType,
uint256 distributionAmount,
address rewardToken
) internal returns (uint256 distributionAmountMinusFees) {
uint256 baseFeesValue = campaignSpecificFees[campaignType];
if (baseFeesValue == 1) baseFeesValue = 0;
else if (baseFeesValue == 0) baseFeesValue = defaultFees;
uint256 _fees = (baseFeesValue * (BASE_9 - feeRebate[msg.sender])) / BASE_9;
distributionAmountMinusFees = distributionAmount;
if (_fees != 0) {
distributionAmountMinusFees = (distributionAmount * (BASE_9 - _fees)) / BASE_9;
address _feeRecipient = feeRecipient;
_feeRecipient = _feeRecipient == address(0) ? address(this) : _feeRecipient;
IERC20(rewardToken).safeTransferFrom(
msg.sender,
_feeRecipient,
distributionAmount - distributionAmountMinusFees
);
}
}
/// @notice Internal version of the `sign` function
function _sign(bytes calldata signature) internal {
bytes32 _messageHash = messageHash;
if (!SignatureChecker.isValidSignatureNow(msg.sender, _messageHash, signature))
revert Errors.InvalidSignature();
userSignatures[msg.sender] = _messageHash;
emit UserSigned(_messageHash, msg.sender);
}
/// @notice Rounds an `epoch` timestamp to the start of the corresponding period
function _getRoundedEpoch(uint32 epoch) internal pure returns (uint32) {
return (epoch / HOUR) * HOUR;
}
/// @notice Internal version of `getCampaignsBetween`
function _getCampaignsBetween(
uint32 start,
uint32 end,
uint32 skip,
uint32 first
) internal view returns (CampaignParameters[] memory, uint256) {
uint256 length;
uint256 campaignListLength = campaignList.length;
uint256 returnSize = first > campaignListLength ? campaignListLength : first;
CampaignParameters[] memory activeRewards = new CampaignParameters[](returnSize);
uint32 i = skip;
while (i < campaignListLength) {
CampaignParameters memory campaignToProcess = campaignList[i];
if (
campaignToProcess.startTimestamp + campaignToProcess.duration > start &&
campaignToProcess.startTimestamp < end
) {
activeRewards[length] = campaignToProcess;
length += 1;
}
unchecked {
++i;
}
if (length == returnSize) break;
}
assembly {
mstore(activeRewards, length)
}
return (activeRewards, i);
}
/// @notice Builds the list of valid reward tokens
function _getValidRewardTokens(
uint32 skip,
uint32 first
) internal view returns (RewardTokenAmounts[] memory, uint256) {
uint256 length;
uint256 rewardTokenListLength = rewardTokens.length;
uint256 returnSize = first > rewardTokenListLength ? rewardTokenListLength : first;
RewardTokenAmounts[] memory validRewardTokens = new RewardTokenAmounts[](returnSize);
uint32 i = skip;
while (i < rewardTokenListLength) {
address token = rewardTokens[i];
uint256 minAmount = rewardTokenMinAmounts[token];
if (minAmount > 0) {
validRewardTokens[length] = RewardTokenAmounts(token, minAmount);
length += 1;
}
unchecked {
++i;
}
if (length == returnSize) break;
}
assembly {
mstore(validRewardTokens, length)
}
return (validRewardTokens, 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[31] private __gap;
}
"
},
"contracts/utils/UUPSHelper.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { IAccessControlManager } from "../interfaces/IAccessControlManager.sol";
import { Errors } from "./Errors.sol";
/// @title UUPSHelper
/// @notice Helper contract for UUPSUpgradeable contracts where the upgradeability is controlled by a specific address
/// @author Angle Labs., Inc
/// @dev The 0 address check in the modifier allows the use of these modifiers during initialization
abstract contract UUPSHelper is UUPSUpgradeable {
modifier onlyGuardianUpgrader(IAccessControlManager _accessControlManager) {
if (address(_accessControlManager) != address(0) && !_accessControlManager.isGovernorOrGuardian(msg.sender))
revert Errors.NotGovernorOrGuardian();
_;
}
modifier onlyGovernorUpgrader(IAccessControlManager _accessControlManager) {
if (address(_accessControlManager) != address(0) && !_accessControlManager.isGovernor(msg.sender))
revert Errors.NotGovernor();
_;
}
constructor() initializer {}
/// @inheritdoc UUPSUpgradeable
function _authorizeUpgrade(address newImplementation) internal virtual override {}
}
"
},
"contracts/interfaces/IAccessControlManager.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;
/// @title IAccessControlManager
/// @author Angle Labs, Inc.
/// @notice Interface for the `AccessControlManager` contracts of Merkl contracts
interface IAccessControlManager {
/// @notice Checks whether an address is governor
/// @param admin Address to check
/// @return Whether the address has the `GOVERNOR_ROLE` or not
function isGovernor(address admin) external view returns (bool);
/// @notice Checks whether an address is a governor or a guardian of a module
/// @param admin Address to check
/// @return Whether the address has the `GUARDIAN_ROLE` or not
/// @dev Governance should make sure when adding a governor to also give this governor the guardian
/// role by calling the `addGovernor` function
function isGovernorOrGuardian(address admin) external view returns (bool);
}
"
},
"contracts/utils/Errors.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.17;
library Errors {
error CampaignDoesNotExist();
error CampaignAlreadyExists();
error CampaignDurationBelowHour();
error CampaignRewardTokenNotWhitelisted();
error CampaignRewardTooLow();
error CampaignShouldStartInFuture();
error InvalidDispute();
error InvalidLengths();
error InvalidOverride();
error InvalidParam();
error InvalidParams();
error InvalidProof();
error InvalidUninitializedRoot();
error InvalidReturnMessage();
error InvalidReward();
error InvalidSignature();
error KeyAlreadyUsed();
error NoDispute();
error NoOverrideForCampaign();
error NotAllowed();
error NotEnoughPayment();
error NotGovernor();
error NotGovernorOrGuardian();
error NotSigned();
error NotTrusted();
error NotUpgradeable();
error NotWhitelisted();
error UnresolvedDispute();
error ZeroAddress();
error DisputeFundsTransferFailed();
error EthNotAccepted();
error ReentrantCall();
error WithdrawalFailed();
error InvalidClaim();
error RefererNotSet();
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol": {
"content": "// 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) ext
Submitted on: 2025-10-06 19:18:49
Comments
Log in to comment.
No comments yet.