// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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:
*
* ```
* 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}:
*
* ```
* 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.
*/
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(uint160(account), 20),
" 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());
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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 "../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:
*
* ```
* 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}:
*
* ```
* 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.
*/
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
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(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(uint160(account), 20),
" 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;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {AffineVault} from "src/vaults/AffineVault.sol";
import {BaseStrategy} from "./BaseStrategy.sol";
import {uncheckedInc} from "src/libs/Unchecked.sol";
contract AccessStrategy is BaseStrategy, AccessControl {
bytes32 public constant STRATEGIST_ROLE = keccak256("STRATEGIST");
constructor(AffineVault _vault, address[] memory strategists) BaseStrategy(_vault) {
// Governance is admin
_grantRole(DEFAULT_ADMIN_ROLE, vault.governance());
_grantRole(STRATEGIST_ROLE, vault.governance());
// Give STRATEGIST_ROLE to every address in list
for (uint256 i = 0; i < strategists.length; i = uncheckedInc(i)) {
_grantRole(STRATEGIST_ROLE, strategists[i]);
}
}
function totalLockedValue() external virtual override returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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
* ====
*
* [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://diligence.consensys.net/posts/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.5.11/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 functionCall(target, data, "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");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(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) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason 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 {
// 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);
}
}
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
contract AffineGovernable {
/// @notice The governance address
address public governance;
modifier onlyGovernance() {
_onlyGovernance();
_;
}
function _onlyGovernance() internal view {
require(msg.sender == governance, "Only Governance.");
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {AffineGovernable} from "src/utils/AffineGovernable.sol";
import {BaseStrategy as Strategy} from "src/strategies/BaseStrategy.sol";
import {uncheckedInc} from "src/libs/Unchecked.sol";
/**
* @notice A core contract to be inherited by the L1 and L2 vault contracts. This contract handles adding
* and removing strategies, investing in (and divesting from) strategies, harvesting gains/losses, and
* strategy liquidation.
*/
contract AffineVault is AffineGovernable, AccessControlUpgradeable {
using SafeTransferLib for ERC20;
/*//////////////////////////////////////////////////////////////
INITIALIZATION
//////////////////////////////////////////////////////////////*/
ERC20 _asset;
/// @notice The token that the vault takes in and tries to get more of, e.g. USDC
function asset() public view virtual returns (address) {
return address(_asset);
}
/**
* @dev Initialize the vault.
* @param _governance The governance address.
* @param vaultAsset The vault's input asset.
*/
function baseInitialize(address _governance, ERC20 vaultAsset) internal virtual {
governance = _governance;
_asset = vaultAsset;
// All roles use the default admin role
// Governance has the admin role and all roles
_grantRole(DEFAULT_ADMIN_ROLE, governance);
_grantRole(HARVESTER, governance);
lastHarvest = uint128(block.timestamp);
}
/*//////////////////////////////////////////////////////////////
AUTHENTICATION
//////////////////////////////////////////////////////////////*/
/// @notice Role with authority to call "harvest", i.e. update this vault's tvl
bytes32 public constant HARVESTER = keccak256("HARVESTER");
/*//////////////////////////////////////////////////////////////
WITHDRAWAL QUEUE
//////////////////////////////////////////////////////////////*/
uint8 constant MAX_STRATEGIES = 20;
/**
* @notice An ordered array of strategies representing the withdrawal queue. The withdrawal queue is used
* whenever the vault wants to pull money out of strategies.
* @dev The first strategy in the array (index 0) is withdrawn from first.
* This is a list of the currently active strategies (all non-zero addresses are active).
*/
Strategy[MAX_STRATEGIES] public withdrawalQueue;
/**
* @notice Gets the full withdrawal queue.
* @return The withdrawal queue.
* @dev This gives easy access to the whole array (by default we can only get one index at a time)
*/
function getWithdrawalQueue() external view returns (Strategy[MAX_STRATEGIES] memory) {
return withdrawalQueue;
}
/**
* @notice Sets a new withdrawal queue.
* @param newQueue The new withdrawal queue.
*/
function setWithdrawalQueue(Strategy[MAX_STRATEGIES] calldata newQueue) external onlyGovernance {
// Maintain queue size
require(newQueue.length == MAX_STRATEGIES, "BV: bad qu size");
// Replace the withdrawal queue.
withdrawalQueue = newQueue;
emit WithdrawalQueueSet(newQueue);
}
/**
* @notice Emitted when the withdrawal queue is updated.
* @param newQueue The new withdrawal queue.
*/
event WithdrawalQueueSet(Strategy[MAX_STRATEGIES] newQueue);
/*//////////////////////////////////////////////////////////////
STRATEGIES
//////////////////////////////////////////////////////////////*/
/// @notice The total amount of underlying assets held in strategies at the time of the last harvest.
uint256 public totalStrategyHoldings;
struct StrategyInfo {
bool isActive;
uint16 tvlBps;
uint232 balance;
}
/// @notice A map of strategy addresses to details
mapping(Strategy => StrategyInfo) public strategies;
uint256 constant MAX_BPS = 10_000;
/// @notice The number of bps of the vault's tvl which may be given to strategies (at most MAX_BPS)
uint256 public totalBps;
/// @notice Emitted when a strategy is added by governance
event StrategyAdded(Strategy indexed strategy);
/// @notice Emitted when a strategy is removed by governance
event StrategyRemoved(Strategy indexed strategy);
/**
* @notice Add a strategy
* @param strategy The strategy to add
* @param tvlBps The number of bps of our tvl the strategy will get when funds are distributed to strategies
*/
function addStrategy(Strategy strategy, uint16 tvlBps) external onlyGovernance {
_increaseTVLBps(tvlBps);
strategies[strategy] = StrategyInfo({isActive: true, tvlBps: tvlBps, balance: 0});
// Add strategy to withdrawal queue
withdrawalQueue[MAX_STRATEGIES - 1] = strategy;
emit StrategyAdded(strategy);
_organizeWithdrawalQueue();
}
/// @notice A helper function for increasing `totalBps`. Used when adding strategies or updating strategy allocs
function _increaseTVLBps(uint256 tvlBps) internal {
uint256 newTotalBps = totalBps + tvlBps;
require(newTotalBps <= MAX_BPS, "BV: too many bps");
totalBps = newTotalBps;
}
/**
* @notice Push all zero addresses to the end of the array. This function is used whenever a strategy is
* added or removed from the withdrawal queue
* @dev Relative ordering of non-zero values is maintained.
*/
function _organizeWithdrawalQueue() internal {
// number or empty values we've seen iterating from left to right
uint256 offset;
for (uint256 i = 0; i < MAX_STRATEGIES; i = uncheckedInc(i)) {
Strategy strategy = withdrawalQueue[i];
if (address(strategy) == address(0)) {
offset += 1;
} else if (offset > 0) {
// index of first empty value seen takes on value of `strategy`
withdrawalQueue[i - offset] = strategy;
withdrawalQueue[i] = Strategy(address(0));
}
}
}
/**
* @notice Remove a strategy from the withdrawal queue. Fully divest from the strategy.
* @param strategy The strategy to remove
* @dev removeStrategy MUST be called with harvest via multicall. This helps get the most accurate tvl numbers
* and allows us to add any realized profits to our lockedProfit
*/
function removeStrategy(Strategy strategy) external onlyGovernance {
for (uint256 i = 0; i < MAX_STRATEGIES; i = uncheckedInc(i)) {
if (strategy != withdrawalQueue[i]) {
continue;
}
strategies[strategy].isActive = false;
// The vault can re-allocate bps to a new strategy
totalBps -= strategies[strategy].tvlBps;
strategies[strategy].tvlBps = 0;
// Remove strategy from withdrawal queue
withdrawalQueue[i] = Strategy(address(0));
emit StrategyRemoved(strategy);
_organizeWithdrawalQueue();
// Take all money out of strategy.
_withdrawFromStrategy(strategy, strategy.totalLockedValue());
break;
}
}
/**
* @notice Update tvl bps assigned to the given list of strategies
* @param strategyList The list of strategies
* @param strategyBps The new bps
*/
function updateStrategyAllocations(Strategy[] calldata strategyList, uint16[] calldata strategyBps)
external
onlyRole(HARVESTER)
{
for (uint256 i = 0; i < strategyList.length; i = uncheckedInc(i)) {
// Get the strategy at the current index.
Strategy strategy = strategyList[i];
// Ignore inactive (removed) strategies
if (!strategies[strategy].isActive) continue;
// update tvl bps
totalBps -= strategies[strategy].tvlBps;
_increaseTVLBps(strategyBps[i]);
strategies[strategy].tvlBps = strategyBps[i];
}
emit StrategyAllocsUpdated(strategyList, strategyBps);
}
/**
* @notice Emitted when we update tvl bps for a list of strategies.
* @param strategyList The list of strategies.
* @param strategyBps The new tvl bps for the strategies
*/
event StrategyAllocsUpdated(Strategy[] strategyList, uint16[] strategyBps);
/*//////////////////////////////////////////////////////////////
STRATEGY DEPOSIT/WITHDRAWAL
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted after the Vault deposits into a strategy contract.
* @param strategy The strategy that was deposited into.
* @param assets The amount of assets deposited.
*/
event StrategyDeposit(Strategy indexed strategy, uint256 assets);
/**
* @notice Emitted after the Vault withdraws funds from a strategy contract.
* @param strategy The strategy that was withdrawn from.
* @param assetsRequested The amount of assets we tried to divest from the strategy.
* @param assetsReceived The amount of assets actually withdrawn.
*/
event StrategyWithdrawal(Strategy indexed strategy, uint256 assetsRequested, uint256 assetsReceived);
/// @notice Deposit `assetAmount` amount of `asset` into strategies according to each strategy's `tvlBps`.
function _depositIntoStrategies(uint256 assetAmount) internal {
// All non-zero strategies are active
for (uint256 i = 0; i < MAX_STRATEGIES; i = uncheckedInc(i)) {
Strategy strategy = withdrawalQueue[i];
if (address(strategy) == address(0)) {
break;
}
_depositIntoStrategy(strategy, (assetAmount * strategies[strategy].tvlBps) / MAX_BPS);
}
}
function _depositIntoStrategy(Strategy strategy, uint256 assets) internal {
// Don't allow empty investments
if (assets == 0) return;
// Increase totalStrategyHoldings to account for the deposit.
totalStrategyHoldings += assets;
unchecked {
// Without this the next harvest would count the deposit as profit.
// Cannot overflow as the balance of one strategy can't exceed the sum of all.
strategies[strategy].balance += uint232(assets);
}
// Approve assets to the strategy so we can deposit.
_asset.safeApprove(address(strategy), assets);
// Deposit into the strategy, will revert upon failure
strategy.invest(assets);
emit StrategyDeposit(strategy, assets);
}
/**
* @notice Withdraw a specific amount of underlying tokens from a strategy.
* @dev This is a "best effort" withdrawal. It could potentially withdraw nothing.
* @param strategy The strategy to withdraw from.
* @param assets The amount of underlying tokens to withdraw.
* @return The amount of assets actually received.
*/
function _withdrawFromStrategy(Strategy strategy, uint256 assets) internal returns (uint256) {
// Withdraw from the strategy
uint256 amountWithdrawn = _divest(strategy, assets);
// Without this the next harvest would count the withdrawal as a loss.
// We update the balance to the current tvl because a withdrawal can reduce the tvl by more than the amount
// withdrawn (e.g. fees during a swap)
uint256 oldStratTVL = strategies[strategy].balance;
uint256 newStratTvl = strategy.totalLockedValue();
strategies[strategy].balance = uint232(newStratTvl);
// Decrease totalStrategyHoldings to account for the withdrawal.
// If we haven't harvested in a long time, newStratTvl could be bigger than oldStratTvl
totalStrategyHoldings -= oldStratTVL > newStratTvl ? oldStratTVL - newStratTvl : 0;
emit StrategyWithdrawal({strategy: strategy, assetsRequested: assets, assetsReceived: amountWithdrawn});
return amountWithdrawn;
}
/// @dev A small wrapper around divest(). We try-catch to make sure that a bad strategy does not pause withdrawals.
function _divest(Strategy strategy, uint256 assets) internal returns (uint256) {
try strategy.divest(assets) returns (uint256 amountDivested) {
return amountDivested;
} catch {
return 0;
}
}
/*//////////////////////////////////////////////////////////////
HARVESTING
//////////////////////////////////////////////////////////////*/
/**
* @notice A timestamp representing when the most recent harvest occurred.
* @dev Since the time since the last harvest is used to calculate management fees, this is set
* to `block.timestamp` (instead of 0) during initialization.
*/
uint128 public lastHarvest;
/// @notice The amount of profit *originally* locked after harvesting from a strategy
uint128 public maxLockedProfit;
/// @notice Amount of time in seconds that profit takes to fully unlock. See lockedProfit().
uint256 public constant LOCK_INTERVAL = 24 hours;
/**
* @notice Emitted after a successful harvest.
* @param user The authorized user who triggered the harvest.
* @param strategies The trusted strategies that were harvested.
*/
event Harvest(address indexed user, Strategy[] strategies);
/**
* @notice Harvest a set of trusted strategies.
* @param strategyList The trusted strategies to harvest.
* @dev Will always revert if profit from last harvest has not finished unlocking.
*/
function harvest(Strategy[] calldata strategyList) external onlyRole(HARVESTER) {
// Profit must not be unlocking
require(block.timestamp >= lastHarvest + LOCK_INTERVAL, "BV: profit unlocking");
// Get the Vault's current total strategy holdings.
uint256 oldTotalStrategyHoldings = totalStrategyHoldings;
// Used to store the new total strategy holdings after harvesting.
uint256 newTotalStrategyHoldings = oldTotalStrategyHoldings;
// Used to store the total profit accrued by the strategies.
uint256 totalProfitAccrued;
// Will revert if any of the specified strategies are untrusted.
for (uint256 i = 0; i < strategyList.length; i = uncheckedInc(i)) {
// Get the strategy at the current index.
Strategy strategy = strategyList[i];
// Ignore inactive (removed) strategies
if (!strategies[strategy].isActive) {
continue;
}
// Get the strategy's previous and current balance.
uint232 balanceLastHarvest = strategies[strategy].balance;
uint256 balanceThisHarvest = strategy.totalLockedValue();
// Update the strategy's stored balance.
strategies[strategy].balance = uint232(balanceThisHarvest);
// Increase/decrease newTotalStrategyHoldings based on the profit/loss registered.
// We cannot wrap the subtraction in parenthesis as it would underflow if the strategy had a loss.
newTotalStrategyHoldings = newTotalStrategyHoldings + balanceThisHarvest - balanceLastHarvest;
unchecked {
// Update the total profit accrued while counting losses as zero profit.
// Cannot overflow as we already increased total holdings without reverting.
totalProfitAccrued += balanceThisHarvest > balanceLastHarvest
? balanceThisHarvest - balanceLastHarvest // Profits since last harvest.
: 0; // If the strategy registered a net loss we don't have any new profit.
}
}
// Update max unlocked profit based on any remaining locked profit plus new profit.
maxLockedProfit = uint128(lockedProfit() + totalProfitAccrued);
// Set strategy holdings to our new total.
totalStrategyHoldings = newTotalStrategyHoldings;
// Assess fees (using old lastHarvest) and update the last harvest timestamp.
_assessFees();
lastHarvest = uint128(block.timestamp);
emit Harvest(msg.sender, strategyList);
}
/**
* @notice Current locked profit amount.
* @dev Profit unlocks uniformly over `LOCK_INTERVAL` seconds after the last harvest
*/
function lockedProfit() public view virtual returns (uint256) {
if (block.timestamp >= lastHarvest + LOCK_INTERVAL) {
return 0;
}
uint256 unlockedProfit = (maxLockedProfit * (block.timestamp - lastHarvest)) / LOCK_INTERVAL;
return maxLockedProfit - unlockedProfit;
}
/*//////////////////////////////////////////////////////////////
LIQUIDATION/REBALANCING
//////////////////////////////////////////////////////////////*/
/// @notice The total amount of the underlying asset the vault has.
function vaultTVL() public view returns (uint256) {
return _asset.balanceOf(address(this)) + totalStrategyHoldings;
}
/**
* @notice Emitted when the vault must make a certain amount of assets available
* @dev We liquidate during cross chain rebalancing or withdrawals.
* @param assetsRequested The amount we wanted to make available for withdrawal.
* @param assetsLiquidated The amount we actually liquidated.
*/
event Liquidation(uint256 assetsRequested, uint256 assetsLiquidated);
/**
* @notice Withdraw `amount` of underlying asset from strategies.
* @dev Always check the return value when using this function, we might not liquidate anything!
* @param amount The amount we want to liquidate
* @return The amount we actually liquidated
*/
function _liquidate(uint256 amount) internal returns (uint256) {
uint256 amountLiquidated;
for (uint256 i = 0; i < MAX_STRATEGIES; i = uncheckedInc(i)) {
Strategy strategy = withdrawalQueue[i];
if (address(strategy) == address(0)) {
break;
}
uint256 balance = _asset.balanceOf(address(this));
if (balance >= amount) {
break;
}
uint256 amountNeeded = amount - balance;
amountNeeded = Math.min(amountNeeded, strategies[strategy].balance);
// Force withdraw of token from strategy
uint256 withdrawn = _withdrawFromStrategy(strategy, amountNeeded);
amountLiquidated += withdrawn;
}
emit Liquidation({assetsRequested: amount, assetsLiquidated: amountLiquidated});
return amountLiquidated;
}
/**
* @notice Assess fees.
* @dev This is called during harvest() to assess management fees.
*/
function _assessFees() internal virtual {}
/**
* @notice Emitted when we do a strategy rebalance, i.e. when we make the strategy tvls match their tvl bps
* @param caller The caller
*/
event Rebalance(address indexed caller);
/// @notice Rebalance strategies according to given tvl bps
function rebalance() external onlyRole(HARVESTER) {
uint256 tvl = vaultTVL();
// Loop through all strategies. Divesting from those whose tvl is too high,
// Invest in those whose tvl is too low
// MAX_STRATEGIES is always equal to withdrawalQueue.length
uint256[MAX_STRATEGIES] memory amountsToInvest;
for (uint256 i = 0; i < MAX_STRATEGIES; i = uncheckedInc(i)) {
Strategy strategy = withdrawalQueue[i];
if (address(strategy) == address(0)) {
break;
}
uint256 idealStrategyTVL = (tvl * strategies[strategy].tvlBps) / MAX_BPS;
uint256 currStrategyTVL = strategy.totalLockedValue();
if (idealStrategyTVL < currStrategyTVL) {
_withdrawFromStrategy(strategy, currStrategyTVL - idealStrategyTVL);
}
if (idealStrategyTVL > currStrategyTVL) {
amountsToInvest[i] = idealStrategyTVL - currStrategyTVL;
}
}
// Loop through the strategies to invest in, and invest in them
for (uint256 i = 0; i < MAX_STRATEGIES; i = uncheckedInc(i)) {
uint256 amountToInvest = amountsToInvest[i];
if (amountToInvest == 0) {
continue;
}
// We aren't guaranteed that the vault has `amountToInvest` since there can be slippage
// when divesting from strategies
// NOTE: Strategies closer to the start of the queue are more likely to get the exact
// amount of money needed
amountToInvest = Math.min(amountToInvest, _asset.balanceOf(address(this)));
if (amountToInvest == 0) {
break;
}
// Deposit into strategy, making sure to not count this investment as a profit
_depositIntoStrategy(withdrawalQueue[i], amountToInvest);
}
emit Rebalance(msg.sender);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {AffineVault} from "src/vaults/AffineVault.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
/// @notice Base strategy contract
abstract contract BaseStrategy {
using SafeTransferLib for ERC20;
constructor(AffineVault _vault) {
vault = _vault;
asset = ERC20(_vault.asset());
}
/// @notice The vault which will deposit/withdraw from the this contract
AffineVault public immutable vault;
modifier onlyVault() {
require(msg.sender == address(vault), "BS: only vault");
_;
}
modifier onlyGovernance() {
require(msg.sender == vault.governance(), "BS: only governance");
_;
}
/// @notice Returns the underlying ERC20 asset the strategy accepts.
ERC20 public immutable asset;
/// @notice Strategy's balance of underlying asset.
/// @return assets Strategy's balance.
function balanceOfAsset() public view returns (uint256 assets) {
assets = asset.balanceOf(address(this));
}
/// @notice Deposit vault's underlying asset into strategy.
/// @param amount The amount to invest.
/// @dev This function must revert if investment fails.
function invest(uint256 amount) external {
asset.safeTransferFrom(msg.sender, address(this), amount);
_afterInvest(amount);
}
/// @notice After getting money from the vault, do something with it.
/// @param amount The amount received from the vault.
/// @dev Since investment is often gas-intensive and may require off-chain data, this will often be unimplemented.
/// @dev Strategists will call custom functions for handling deployment of capital.
function _afterInvest(uint256 amount) internal virtual {}
/// @notice Withdraw vault's underlying asset from strategy.
/// @param amount The amount to withdraw.
/// @return The amount of `asset` divested from the strategy
function divest(uint256 amount) external onlyVault returns (uint256) {
return _divest(amount);
}
/// @dev This function should not revert if we get less than `amount` out of the strategy
function _divest(uint256 amount) internal virtual returns (uint256) {}
/// @notice The total amount of `asset` that the strategy is managing
/// @dev This should not overestimate, and should account for slippage during divestment
/// @return The strategy tvl
function totalLockedValue() external virtual returns (uint256);
function sweep(ERC20 token) external onlyGovernance {
token.safeTransfer(vault.governance(), token.balanceOf(address(this)));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
import "../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;
}
/**
* @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;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {AffineVault} from "src/vaults/AffineVault.sol";
import {AccessStrategy} from "src/strategies/AccessStrategy.sol";
import {ICurvePool, I3CrvMetaPoolZap} from "src/interfaces/curve.sol";
import {IConvexBooster, IConvexRewards} from "src/interfaces/convex.sol";
import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
contract ConvexStrategy is AccessStrategy {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/// @notice Index assigned to `asset` by curve. Used during deposit/withdraw.
int128 public immutable assetIndex;
/// @notice The `asset`'s decimals.
uint8 immutable assetDecimals;
constructor(
AffineVault _vault,
int128 _assetIndex,
bool _isMetaPool,
ICurvePool _curvePool,
I3CrvMetaPoolZap _zapper,
uint256 _convexPid,
address[] memory strategists
) AccessStrategy(_vault, strategists) {
assetIndex = _assetIndex;
assetDecimals = ERC20(_vault.asset()).decimals();
isMetaPool = _isMetaPool;
curvePool = _curvePool;
zapper = _zapper;
convexPid = _convexPid;
if (isMetaPool) require(address(zapper) != address(0), "Zapper required");
IConvexBooster.PoolInfo memory poolInfo = CVX_BOOSTER.poolInfo(convexPid);
cvxRewarder = IConvexRewards(poolInfo.crvRewards);
curveLpToken = ERC20(poolInfo.lptoken);
// Curve Approvals (minting/burning lp tokens)
if (isMetaPool) {
asset.safeApprove(address(zapper), type(uint256).max);
curveLpToken.safeApprove(address(zapper), type(uint256).max);
} else {
asset.safeApprove(address(curvePool), type(uint256).max);
}
// Convex Approvals
curveLpToken.safeApprove(address(CVX_BOOSTER), type(uint256).max);
// For trading CVX and CRV
CRV.safeApprove(address(ROUTER), type(uint256).max);
CVX.safeApprove(address(ROUTER), type(uint256).max);
}
/*//////////////////////////////////////////////////////////////
DEPOSITS
//////////////////////////////////////////////////////////////*/
/// @notice The curve pool, e.g. FRAX/USDC. Stableswap pool addresses are different from their lp token addresses.
/// @dev https://curve.readthedocs.io/exchange-deposits.html#curve-stableswap-exchange-deposit-contracts
ICurvePool public immutable curvePool;
/// @notice The lp token received upon deposit into the pool.
ERC20 public immutable curveLpToken;
/// @notice If true, use the zapper for deposits/withdrawals E.g. for MIM/3CRV this would be true.
bool immutable isMetaPool;
/// @notice Allows users to deposit underlying tokens to metapool, e.g. USDC to MIM/3CRV.
/// @dev See https://curve.readthedocs.io/exchange-deposits.html#metapool-deposits
I3CrvMetaPoolZap public immutable zapper;
/// @notice Id of the curve pool (used by convex booster).
uint256 public immutable convexPid;
/// @notice Convex booster. Used for depositing curve lp tokens.
IConvexBooster public constant CVX_BOOSTER = IConvexBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31);
/// @notice Deposit `assets` and receive at least `minLpTokens` of CRV lp tokens. Also stake lp tokens in Convex.
function deposit(uint256 assets, uint256 minLpTokens) external onlyRole(STRATEGIST_ROLE) {
_depositIntoCurve(assets, minLpTokens);
_depositIntoConvex();
}
/// @dev Deposits into curve using metapool if necessary.
function _depositIntoCurve(uint256 assets, uint256 minLpTokens) internal {
// E.g. in a FRAX-USDC stableswap pool, the 0 index is for FRAX and the index 1 is for USDC.
if (isMetaPool) {
uint256[4] memory depositAmounts = [uint256(0), 0, 0, 0];
depositAmounts[uint256(uint128(assetIndex))] = assets;
zapper.add_liquidity({pool: address(curvePool), depositAmounts: depositAmounts, minMintAmount: minLpTokens});
} else {
uint256[2] memory depositAmounts = [uint256(0), 0];
depositAmounts[uint256(uint128(assetIndex))] = assets;
curvePool.add_liquidity({depositAmounts: depositAmounts, minMintAmount: minLpTokens});
}
}
function _depositIntoConvex() internal {
CVX_BOOSTER.depositAll(convexPid, true);
}
/// @notice Deposit assets into curve without staking in convex.
function depositIntoCurve(uint256 assets, uint256 minLpTokens) external onlyRole(STRATEGIST_ROLE) {
_depositIntoCurve(assets, minLpTokens);
}
/// @notice Deposit curve lp tokens into convex.
function depositIntoConvex() external onlyRole(STRATEGIST_ROLE) {
_depositIntoConvex();
}
/// @dev Divestment. Unstake from convex and convert curve lp tokens into `asset`.
function _divest(uint256 assets) internal override returns (uint256) {
_withdraw(assets);
uint256 amountToSend = Math.min(asset.balanceOf(address(this)), assets);
asset.safeTransfer(address(vault), amountToSend);
return amountToSend;
}
/*//////////////////////////////////////////////////////////////
WITHDRAWALS
//////////////////////////////////////////////////////////////*/
/**
* @notice The minimum amount of lp tokens to count towards tvl or be liquidated during withdrawals.
* @dev `calc_withdraw_one_coin` and `remove_liquidity_one_coin` may fail when using amounts smaller than this.
*/
uint256 constant MIN_LP_AMOUNT = 100;
/**
* @notice Withdraw from CRV + convex and try to increase balance of `asset` to `assets`.
* @dev Useful in the case that we want to do multiple withdrawals ahead of a big divestment from the vault. Doing the
* withdrawals manually (in chunks) will give us less slippage
*/
function withdrawAssets(uint256 assets) external onlyRole(STRATEGIST_ROLE) {
_withdraw(assets);
}
/// @dev Try to increase balance of `asset` to `assets`.
function _withdraw(uint256 assets) internal {
uint256 currAssets = asset.balanceOf(address(this));
if (currAssets >= assets) return;
// price * (num of lp tokens) = dollars
uint256 currLpBal = curveLpToken.balanceOf(address(this));
uint256 lpTokenBal = currLpBal + cvxRewarder.balanceOf(address(this));
uint256 price = curvePool.get_virtual_price(); // 18 decimals
uint256 dollarsOfLp = lpTokenBal.mulWadDown(price);
// Get the amount of dollars to remove from vault, and the equivalent amount of lp token.
// We assume that the vault `asset` is $1.00 (e.g. we assume that USDC is 1.00). Convert to 18 decimals.
uint256 dollarsOfAssetsToDivest = Math.min(_giveDecimals(assets - currAssets, assetDecimals, 18), dollarsOfLp);
uint256 lpTokensToDivest = dollarsOfAssetsToDivest.divWadDown(price);
// Minimum amount of dollars received is 99% of dollar value of lp shares (trading fees, slippage)
// Convert back to `asset` decimals.
uint256 minAssetsReceived = _giveDecimals(dollarsOfAssetsToDivest.mulDivDown(99, 100), 18, assetDecimals);
// Increase the cap on lp tokens by 1% to account for curve's trading fees
uint256 maxLpTokensToBurn = Math.min(lpTokenBal, lpTokensToDivest.mulDivDown(101, 100));
// Withdraw from convex if needed to get correct amount of curve lp tokens
if (maxLpTokensToBurn > currLpBal) _withdrawFromConvex(maxLpTokensToBurn - currLpBal);
_withdrawFromCurve(maxLpTokensToBurn, minAssetsReceived);
}
/// @dev Withdraw from curve burning at most `maxLpTokensToBurn` and receiving at least `minAssetsReceived` assets.
function _withdrawFromCurve(uint256 maxLpTokensToBurn, uint256 minAssetsReceived) internal {
if (maxLpTokensToBurn < MIN_LP_AMOUNT) return;
if (isMetaPool) {
zapper.remove_liquidity_one_coin({
pool: address(curvePool),
burnAmount: maxLpTokensToBurn,
index: assetIndex,
minAmount: minAssetsReceived
});
} else {
curvePool.remove_liquidity_one_coin(curveLpToken.balanceOf(address(this)), assetIndex, minAssetsReceived);
}
}
function _withdrawFromConvex(uint256 numLpTokens) internal {
cvxRewarder.withdrawAndUnwrap({amount: numLpTokens, claim: true});
}
function withdrawFromCurve(uint256 maxLpTokensToBurn, uint256 minAssetsReceived)
external
onlyRole(STRATEGIST_ROLE)
{
_withdrawFromCurve(maxLpTokensToBurn, minAssetsReceived);
}
function withdrawFromConvex(uint256 numLpTokens) external onlyRole(STRATEGIST_ROLE) {
_withdrawFromConvex(numLpTokens);
}
/// @dev Convert between different decimal representations
function _giveDecimals(uint256 number, uint256 inDecimals, uint256 outDecimals) internal pure returns (uint256) {
if (inDecimals > outDecimals) {
number = number / (10 ** (inDecimals - outDecimals));
}
if (inDecimals < outDecimals) {
number = number * (10 ** (outDecimals - inDecimals));
}
return number;
}
/*//////////////////////////////////////////////////////////////
REWARDS
//////////////////////////////////////////////////////////////*/
/// @notice BaseRewardPool. Used for claiming rewards (cvx and crv).
IConvexRewards public immutable cvxRewarder;
/// @notice The UniswapV2 router used for trades.
ISwapRouter public constant ROUTER = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
/// @notice The CRV token. Given as a reward for deposits into convex contracts.
ERC20 public constant CRV = ERC20(0xD533a949740bb3306d119CC777fa900bA034cd52);
/// @notice The CVX token. Given as a reward for deposits into convex contracts.
ERC20 public constant CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B);
/// @notice All trades are made with WETH as the middle asset, e.g. CRV => WETH => USDC.
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @notice We won't sell any CRV or CVX unless we have 0.01 of them.
uint256 constant MIN_REWARD_AMOUNT = 0.01e18;
/// @notice Claim convex rewards.
function claimRewards() external onlyRole(STRATEGIST_ROLE) {
_claimRewards();
}
function _claimRewards() internal {
cvxRewarder.getReward();
}
/// @notice Sell rewards.
function sellRewards(uint256 minAssetsFromCrv, uint256 minAssetsFromCvx) external onlyRole(STRATEGIST_ROLE) {
_sellRewards(minAssetsFromCrv, minAssetsFromCvx);
}
function _sellRewards(uint256 minAssetsFromCrv, uint256 minAssetsFromCvx) internal {
// Sell CRV rewards if we have at least MIN_REWARD_AMOUNT tokens
uint256 crvBal = CRV.balanceOf(address(this));
uint24 bps100 = 10_000;
uint24 bps5 = 500;
if (crvBal > MIN_REWARD_AMOUNT) {
ISwapRouter.ExactInputSingleParams memory paramsCrv = ISwapRouter.ExactInputSingleParams({
tokenIn: address(CRV),
tokenOut: address(asset),
fee: bps100,
recipient: address(this),
deadline: block.timestamp,
amountIn: crvBal,
amountOutMinimum: minAssetsFromCrv,
sqrtPriceLimitX96: 0
});
ROUTER.exactInputSingle(paramsCrv);
}
// Sell CVX rewards
uint256 cvxBal = CVX.balanceOf(address(this));
if (cvxBal > MIN_REWARD_AMOUNT) {
ISwapRouter.ExactInputParams memory paramsCvx = ISwapRouter.ExactInputParams({
path: abi.encodePacked(address(CVX), bps100, address(WETH), bps5, address(asset)),
recipient: address(this),
deadline: block.timestamp,
amountIn: cvxBal,
amountOutMinimum: minAssetsFromCvx
});
ROUTER.exactInput(paramsCvx);
}
}
/// @notice Claim convex rewards and sell them for `asset`.
function claimAndSellRewards(uint256 minAssetsFromCrv, uint256 minAssetsFromCvx)
external
onlyRole(STRATEGIST_ROLE)
{
_claimRewards();
_sellRewards(minAssetsFromCrv, minAssetsFromCvx);
}
/// @notice Amount of CRV to be gained during next claim.
/// @dev The amount of CVX to be gained can be calculated off chain: https://docs.convexfinance.com/convexfinanceintegration/cvx-minting
function pendingRewards() external view returns (uint256 pendingCrv) {
pendingCrv = cvxRewarder.earned(address(this));
}
/*//////////////////////////////////////////////////////////////
TVL
//////////////////////////////////////////////////////////////*/
function totalLockedValue() external override returns (uint256) {
uint256 lpTokenBal = curveLpToken.balanceOf(address(this)) + cvxRewarder.balanceOf(address(this));
uint256 assetsLp;
if (lpTokenBal < MIN_LP_AMOUNT) return balanceOfAsset();
if (isMetaPool) {
assetsLp =
zapper.calc_withdraw_one_coin({pool: address(curvePool), tokenAmount: lpTokenBal, index: assetIndex});
} else {
assetsLp = curvePool.calc_withdraw_one_coin(lpTokenBal, assetIndex);
}
return balanceOfAsset() + assetsLp;
}
/*//////////////////////////////////////////////////////////////
UPGRADES
//////////////////////////////////////////////////////////////*/
function sendAllTokens(address newStrategy) external onlyRole(DEFAULT_ADMIN_ROLE) {
// Withdraw from convex
uint256 stakedBal = cvxRewarder.balanceOf(address(this));
if (stakedBal > 0) {
_withdrawFromConvex(stakedBal);
}
// Send `asset`, curve lp, cvx, crv tokens to the new address
_sweepToken(asset, newStrategy);
_sweepToken(curveLpToken, newStrategy);
_sweepToken(CRV, newStrategy);
_sweepToken(CVX, newStrategy);
}
function _sweepToken(ERC20 token, address to) internal {
uint256 bal = token.balanceOf(address(this));
if (bal == 0) return;
token.safeTransfer(to, bal);
}
}
// 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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165Upgradeable.sol";
import "../../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;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// Divide z by the denominator.
z := div(z, denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
// Start off with z at 1.
z := 1
// Used below to help find a nearby power of 2.
let y := x
// Find the lowest power of 2 that is at least sqrt(x).
if iszero(lt(y, 0x100000000000000000000000000000000)) {
y := shr(128, y) // Like dividing by 2 ** 128.
z := shl(64, z) // Like multiplying by 2 ** 64.
}
if iszero(lt(y, 0x10000000000000000)) {
y := shr(64, y) // Like dividing by 2 ** 64.
z := shl(32, z) // Like multiplying by 2 ** 32.
}
if iszero(lt(y, 0x100000000)) {
y := shr(32, y) // Like dividing by 2 ** 32.
z := shl(16, z) // Like multiplying by 2 ** 16.
}
if iszero(lt(y, 0x10000)) {
y := shr(16, y) // Like dividing by 2 ** 16.
z := shl(8, z) // Like multiplying by 2 ** 8.
}
if iszero(lt(y, 0x100)) {
y := shr(8, y) // Like dividing by 2 ** 8.
z := shl(4, z) // Like multiplying by 2 ** 4.
}
if iszero(lt(y, 0x10)) {
y := shr(4, y) // Like dividing by 2 ** 4.
z := shl(2, z) // Like multiplying by 2 ** 2.
}
if iszero(lt(y, 0x8)) {
// Equivalent to 2 ** z.
z := shl(1, z)
}
// Shifting right by 1 is like dividing by 2.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// Compute a rounded down version of z.
let zRoundDown := div(x, z)
// If zRoundDown is smaller, use it.
if lt(zRoundDown, z) {
z := zRoundDown
}
}
}
}
// 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;
}
// 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;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
// See https://docs.convexfinance.com/convexfinanceintegration/booster
interface IConvexBooster {
struct PoolInfo {
address lptoken;
address token;
address gauge;
address crvRewards;
address stash;
bool shutdown;
}
function poolLength() external returns (uint256);
function poolInfo(uint256 _pid) external returns (PoolInfo memory);
function depositAll(uint256 _pid, bool _stake) external returns (bool);
function crv() external returns (address);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
// See https://docs.convexfinance.com/convexfinanceintegration/baserewardpool
interface IConvexRewards {
// get balance of an address
function balanceOf(address _account) external returns (uint256);
//withdraw to a convex tokenized deposit
function withdraw(uint256 _amount, bool _claim) external returns (bool);
//withdraw directly to curve LP token
function withdrawAndUnwrap(uint256 amount, bool claim) external returns (bool);
// Withdraw to curve LP token while also claiming any rewards
function withdrawAllAndUnwrap(bool claim) external;
// claim rewards
function getReward() external returns (bool);
//stake a convex tokenized deposit
function stake(uint256 _amount) external returns (bool);
//stake a convex tokenized deposit for another address(transferring ownership)
function stakeFor(address _account, uint256 _amount) external returns (bool);
// Get amount of pending CRV rewards
function earned(address account) external view returns (uint256);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
// See https://curve.readthedocs.io/exchange-deposits.html#curve-stableswap-exchange-deposit-contracts
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
interface ICurvePool {
function lp_token() external view returns (address);
function add_liquidity(uint256[2] memory depositAmounts, uint256 minMintAmount) external returns (uint256);
function remove_liquidity_one_coin(uint256 _burn_amount, int128 i, uint256 _min_amount)
external
returns (uint256);
function remove_liquidity_imbalance(uint256[2] memory _amounts, uint256 _max_burn_amount)
external
returns (uint256);
function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256);
function get_virtual_price() external view returns (uint256);
}
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
// 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);
}
// 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);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
// See https://etherscan.io/address/0xd8b712d29381748dB89c36BCa0138d7c75866ddF#code
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
interface ILiquidityGauge {
function deposit(uint256 _value) external;
function withdraw(uint256 _value) external returns (uint256);
function balanceOf(address owner) external returns (uint256);
// Get claimable CRV
function claimable_tokens(address addr) external returns (uint256);
function claim_rewards() external;
// NOTE: The below only applies to non-CRV rewards
// claimable_reward - claimed_reward = pending rewards
// Total rewards, claimed and unclaimed
function claimable_reward(address addr, address token) external view returns (uint256);
// unclaimed
function claimed_reward(address addr, address token) external view returns (uint256);
}
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
// See https://etherscan.io/address/0xa79828df1850e8a3a3064576f380d90aecdd3359#code for an example
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
interface I3CrvMetaPoolZap {
function add_liquidity(address pool, uint256[4] memory depositAmounts, uint256 minMintAmount)
external
returns (uint256);
function remove_liquidity_one_coin(address pool, uint256 burnAmount, int128 index, uint256 minAmount)
external
returns (uint256);
function remove_liquidity_imbalance(address _pool, uint256[4] memory _amounts, uint256 _maxBurnAmount)
external
returns (uint256);
function calc_withdraw_one_coin(address pool, uint256 tokenAmount, int128 index) external view returns (uint256);
}
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
// See https://etherscan.io/address/0xd061D61a4d941c39E5453435B6345Dc261C2fcE0#code
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
interface IMinter {
function mint(address gauge) external;
}
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';
/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
/// @return amountOut The amount of the received token
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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]
* ```
* 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. Equivalent to `reinitializer(1)`.
*/
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.
*
* `initializer` is equivalent to `reinitializer(1)`, so 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.
*
* 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.
*/
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.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized < type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 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. It 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)`.
// We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`.
// This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`.
// Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a
// good first aproximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1;
uint256 x = a;
if (x >> 128 > 0) {
x >>= 128;
result <<= 64;
}
if (x >> 64 > 0) {
x >>= 64;
result <<= 32;
}
if (x >> 32 > 0) {
x >>= 32;
result <<= 16;
}
if (x >> 16 > 0) {
x >>= 16;
result <<= 8;
}
if (x >> 8 > 0) {
x >>= 8;
result <<= 4;
}
if (x >> 4 > 0) {
x >>= 4;
result <<= 2;
}
if (x >> 2 > 0) {
result <<= 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) {
uint256 result = sqrt(a);
if (rounding == Rounding.Up && result * result < a) {
result += 1;
}
return result;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_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) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @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] = _HEX_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);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library StringsUpgradeable {
bytes16 private constant _HEX_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) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @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] = _HEX_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);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
/* solhint-disable func-visibility */
function uncheckedInc(uint256 i) pure returns (uint256) {
unchecked {
return i + 1;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
import {IConvexBooster} from "./convex/IConvexBooster.sol";
import {IConvexRewards} from "./convex/IConvexRewards.sol";
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.16;
import {ICurvePool} from "./curve/ICurvePool.sol";
import {ILiquidityGauge} from "./curve/ILiquidityGauge.sol";
import {I3CrvMetaPoolZap} from "./curve/IMetaPoolZap.sol";
import {IMinter} from "./curve/IMinter.sol";
{
"compilationTarget": {
"src/strategies/ConvexStrategy.sol": "ConvexStrategy"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 2000000
},
"remappings": [
":@opengsn/=node_modules/@opengsn/",
":@openzeppelin/=node_modules/@openzeppelin/",
":@uniswap/=node_modules/@uniswap/",
":base64-sol/=node_modules/base64-sol/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":hardhat/=node_modules/hardhat/",
":script/=script/",
":solady/=node_modules/solady/",
":solmate/=node_modules/solmate/",
":src/=src/"
]
}
[{"inputs":[{"internalType":"contract AffineVault","name":"_vault","type":"address"},{"internalType":"int128","name":"_assetIndex","type":"int128"},{"internalType":"bool","name":"_isMetaPool","type":"bool"},{"internalType":"contract ICurvePool","name":"_curvePool","type":"address"},{"internalType":"contract I3CrvMetaPoolZap","name":"_zapper","type":"address"},{"internalType":"uint256","name":"_convexPid","type":"uint256"},{"internalType":"address[]","name":"strategists","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"CRV","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CVX","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CVX_BOOSTER","outputs":[{"internalType":"contract IConvexBooster","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROUTER","outputs":[{"internalType":"contract ISwapRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STRATEGIST_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetIndex","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"balanceOfAsset","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"minAssetsFromCrv","type":"uint256"},{"internalType":"uint256","name":"minAssetsFromCvx","type":"uint256"}],"name":"claimAndSellRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"convexPid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"curveLpToken","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"curvePool","outputs":[{"internalType":"contract ICurvePool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cvxRewarder","outputs":[{"internalType":"contract IConvexRewards","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"uint256","name":"minLpTokens","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositIntoConvex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"uint256","name":"minLpTokens","type":"uint256"}],"name":"depositIntoCurve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"divest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"invest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingRewards","outputs":[{"internalType":"uint256","name":"pendingCrv","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"minAssetsFromCrv","type":"uint256"},{"internalType":"uint256","name":"minAssetsFromCvx","type":"uint256"}],"name":"sellRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newStrategy","type":"address"}],"name":"sendAllTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"}],"name":"sweep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalLockedValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"contract AffineVault","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"withdrawAssets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"numLpTokens","type":"uint256"}],"name":"withdrawFromConvex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxLpTokensToBurn","type":"uint256"},{"internalType":"uint256","name":"minAssetsReceived","type":"uint256"}],"name":"withdrawFromCurve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"zapper","outputs":[{"internalType":"contract I3CrvMetaPoolZap","name":"","type":"address"}],"stateMutability":"view","type":"function"}]