// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
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 returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @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 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 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 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 `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @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 Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert Errors.FailedCall();
}
}
/**
* @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 or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* 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.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @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`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";
import {EnumerableMap} from "openzeppelin-contracts/utils/structs/EnumerableMap.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ProtocolNotSupported, ProtocolInUse, ZeroAddress, TokenOutNotAllowed} from "../errors/scErrors.sol";
import {Constants as C} from "../lib/Constants.sol";
import {IVault} from "../interfaces/balancer/IVault.sol";
import {IFlashLoanRecipient} from "../interfaces/balancer/IFlashLoanRecipient.sol";
import {IAdapter} from "./IAdapter.sol";
import {sc4626} from "../sc4626.sol";
import {IPriceConverter} from "./priceConverter/IPriceConverter.sol";
import {ISwapper} from "./swapper/ISwapper.sol";
/**
* @title BaseV2Vault
* @notice Base vault contract for v2 vaults to that use multiple lending markets thru adapters.
*/
abstract contract BaseV2Vault is sc4626, IFlashLoanRecipient {
using Address for address;
using SafeTransferLib for ERC20;
using EnumerableMap for EnumerableMap.UintToAddressMap;
event SwapperUpdated(address indexed admin, address newSwapper);
event PriceConverterUpdated(address indexed admin, address newPriceConverter);
event ProtocolAdapterAdded(address indexed admin, uint256 adapterId, address adapter);
event ProtocolAdapterRemoved(address indexed admin, uint256 adapterId);
event RewardsClaimed(uint256 adapterId);
event TokenSwapped(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutReceived);
event TokenWhitelisted(address token, bool value);
// Balancer vault for flashloans
IVault public constant balancerVault = IVault(C.BALANCER_VAULT);
// price converter contract
IPriceConverter public priceConverter;
// swapper contract for facilitating token swaps
ISwapper public swapper;
// mapping of IDs to lending protocol adapter contracts
EnumerableMap.UintToAddressMap internal protocolAdapters;
// mapping for the tokens allowed for swapping
mapping(ERC20 => bool) internal swapWhitelist;
constructor(
address _admin,
address _keeper,
ERC20 _asset,
IPriceConverter _priceConverter,
ISwapper _swapper,
string memory _name,
string memory _symbol
) sc4626(_admin, _keeper, _asset, _name, _symbol) {
_setPriceConverter(_priceConverter);
_setSwapper(_swapper);
swapWhitelist[_asset] = true;
}
/**
* @notice whitelist (or cancel whitelist) a token to be swapped out
* @param _token The token to whitelist
* @param _value whether to whitelist or cancel whitelist
*/
function whiteListToken(ERC20 _token, bool _value) external {
_onlyAdmin();
if (address(_token) == address(0)) revert ZeroAddress();
swapWhitelist[_token] = _value;
emit TokenWhitelisted(address(_token), _value);
}
/**
* @notice Set the swapper contract used for executing token swaps.
* @param _newSwapper The new swapper contract.
*/
function setSwapper(ISwapper _newSwapper) external {
_onlyAdmin();
_setSwapper(_newSwapper);
emit SwapperUpdated(msg.sender, address(_newSwapper));
}
/**
* @notice Set the price converter contract used for executing token swaps.
* @param _newPriceConverter The new price converter contract.
*/
function setPriceConverter(IPriceConverter _newPriceConverter) external {
_onlyAdmin();
_setPriceConverter(_newPriceConverter);
emit PriceConverterUpdated(msg.sender, address(_newPriceConverter));
}
/**
* @notice Add a new protocol adapter to the vault.
* @param _adapter The adapter to add.
*/
function addAdapter(IAdapter _adapter) external {
_onlyAdmin();
uint256 id = _adapter.id();
if (isSupported(id)) revert ProtocolInUse(id);
protocolAdapters.set(id, address(_adapter));
address(_adapter).functionDelegateCall(abi.encodeWithSelector(IAdapter.setApprovals.selector));
emit ProtocolAdapterAdded(msg.sender, id, address(_adapter));
}
/**
* @notice Remove a protocol adapter from the vault. Reverts if the adapter is in use unless _force is true.
* @param _adapterId The ID of the adapter to remove.
* @param _force Whether or not to force the removal of the adapter.
*/
function removeAdapter(uint256 _adapterId, bool _force) external {
_onlyAdmin();
_isSupportedCheck(_adapterId);
// check if protocol is being used
if (!_force && IAdapter(protocolAdapters.get(_adapterId)).getCollateral(address(this)) > 0) {
revert ProtocolInUse(_adapterId);
}
_adapterDelegateCall(_adapterId, abi.encodeWithSelector(IAdapter.revokeApprovals.selector));
protocolAdapters.remove(_adapterId);
emit ProtocolAdapterRemoved(msg.sender, _adapterId);
}
/**
* @notice Check if a lending market adapter is supported/used.
* @param _adapterId The ID of the lending market adapter.
*/
function isSupported(uint256 _adapterId) public view returns (bool) {
return protocolAdapters.contains(_adapterId);
}
/// @notice returns the adapter address given the adapterId (only if the adaapterId is supported else returns zero address)
/// @param _adapterId the id of the adapter to check
function getAdapter(uint256 _adapterId) external view returns (address adapter) {
(, adapter) = protocolAdapters.tryGet(_adapterId);
}
/**
* @notice returns whether a token is whitelisted to be swapped out or not
*/
function isTokenWhitelisted(ERC20 _token) external view returns (bool) {
return swapWhitelist[_token];
}
/**
* @notice Claim rewards from a lending market.
* @param _adapterId The ID of the lending market adapter.
* @param _callData The encoded data for the claimRewards function.
*/
function claimRewards(uint256 _adapterId, bytes calldata _callData) external {
_onlyKeeperOrFlashLoan();
_isSupportedCheck(_adapterId);
_adapterDelegateCall(_adapterId, abi.encodeWithSelector(IAdapter.claimRewards.selector, _callData));
emit RewardsClaimed(_adapterId);
}
/**
* @notice Sell any token for any whitelisted token on preconfigured exchange in the swapper contract.
* @param _tokenIn Address of the token to swap.
* @param _tokenOut Address of the token to receive.
* @param _amountIn Amount of the token to swap.
* @param _amountOutMin Minimum amount of the token to receive.
* @param _swapData Arbitrary data to pass to the swap router.
*/
function swapTokens(
address _tokenIn,
address _tokenOut,
uint256 _amountIn,
uint256 _amountOutMin,
bytes calldata _swapData
) external {
_onlyKeeperOrFlashLoan();
if (!swapWhitelist[ERC20(_tokenOut)]) revert TokenOutNotAllowed(_tokenOut);
bytes memory result = address(swapper).functionDelegateCall(
abi.encodeWithSelector(
ISwapper.swapTokens.selector, _tokenIn, _tokenOut, _amountIn, _amountOutMin, _swapData
)
);
uint256 amountReceived = abi.decode(result, (uint256));
emit TokenSwapped(_tokenIn, _tokenOut, _amountIn, amountReceived);
}
function _multiCall(bytes[] memory _callData) internal {
for (uint256 i = 0; i < _callData.length; i++) {
if (_callData[i].length == 0) continue;
address(this).functionDelegateCall(_callData[i]);
}
}
function _adapterDelegateCall(uint256 _adapterId, bytes memory _data) internal {
protocolAdapters.get(_adapterId).functionDelegateCall(_data);
}
function _adapterDelegateCall(address _adapter, bytes memory _data) internal {
_adapter.functionDelegateCall(_data);
}
function _setSwapper(ISwapper _newSwapper) internal {
_zeroAddressCheck(address(_newSwapper));
swapper = _newSwapper;
}
function _setPriceConverter(IPriceConverter _newPriceConverter) internal {
_zeroAddressCheck(address(_newPriceConverter));
priceConverter = _newPriceConverter;
}
function _isSupportedCheck(uint256 _adapterId) internal view {
if (!isSupported(_adapterId)) revert ProtocolNotSupported(_adapterId);
}
function _zeroAddressCheck(address _address) internal pure {
if (_address == address(0)) revert ZeroAddress();
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.13;
library Constants {
uint256 public constant ONE = 1e18;
// decimals difference between WETH and USDC (18 - 6)
uint256 public constant WETH_USDC_DECIMALS_DIFF = 1e12;
uint256 public constant WETH_USDT_DECIMALS_DIFF = 1e12;
// value for the variable interest rate mode on Aave
uint256 public constant AAVE_VAR_INTEREST_RATE_MODE = 2;
// enable efficeincy mode on Aave (used to allow greater LTV when asset and debt tokens are correlated in price)
uint8 public constant AAVE_EMODE_ID = 1;
// vaule used to scale the token's collateral/borrow factors from the euler market
uint32 constant EULER_CONFIG_FACTOR_SCALE = 4_000_000_000;
/*//////////////////////////////////////////////////////////////
MAINNET ADDRESSES
//////////////////////////////////////////////////////////////*/
// address of the USDC token contract
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
// address of the WETH token contract
address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
// address of the wrapped stETH token contract
address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
// address of the Lido stETH token contract
address public constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
// address of the LUSD token contract
address public constant LUSD = 0x5f98805A4E8be255a32880FDeC7F6728C6568bA0;
// address of the DAI token contract
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
// address of the sDAI token contract
address public constant SDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA;
// address of the USDT token contract
address public constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
// address of the Curve pool for ETH-stETH
address public constant CURVE_ETH_STETH_POOL = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022;
// address of the Uniswap v3 swap router contract
address public constant UNISWAP_V3_SWAP_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
// address of the Aave v3 pool contract
address public constant AAVE_V3_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
// address of the Aave pool data provider contract
address public constant AAVE_V3_POOL_DATA_PROVIDER = 0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3;
// address of the Aave v3 "aEthUSDC" token (supply token)
address public constant AAVE_V3_AUSDC_TOKEN = 0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c;
// address of the Aave v3 "aEthUSDT" token (supply token)
address public constant AAVE_V3_AUSDT_TOKEN = 0x23878914EFE38d27C4D67Ab83ed1b93A74D4086a;
// address of the Aave v3 "aEthwstETH" token (supply token)
address public constant AAVE_V3_AWSTETH_TOKEN = 0x0B925eD163218f6662a35e0f0371Ac234f9E9371;
// address of the Aave v3 "variableDebtEthWETH" token (variable debt token)
address public constant AAVE_V3_VAR_DEBT_WETH_TOKEN = 0xeA51d7853EEFb32b6ee06b1C12E6dcCA88Be0fFE;
// address of the Aave v3 "variableDebtEthWETH" token implementation contract (variable debt token)
address public constant AAVE_V3_VAR_DEBT_IMPLEMENTATION_CONTRACT = 0xaC725CB59D16C81061BDeA61041a8A5e73DA9EC6;
address public constant SPARK_POOL = 0xC13e21B648A5Ee794902342038FF3aDAB66BE987;
address public constant SPARK_POOL_DATA_PROVIDER = 0xFc21d6d146E6086B8359705C8b28512a983db0cb;
address public constant SPARK_ADAI_TOKEN = 0x4DEDf26112B3Ec8eC46e7E31EA5e123490B05B8B;
address public constant SPARK_ASDAI_TOKEN = 0x78f897F0fE2d3B5690EbAe7f19862DEacedF10a7;
address public constant SPARK_VAR_DEBT_WETH_TOKEN = 0x2e7576042566f8D6990e07A1B61Ad1efd86Ae70d;
// EULER Contracts
address public constant EULER = 0x27182842E098f60e3D576794A5bFFb0777E025d3;
address public constant EULER_MARKETS = 0x3520d5a913427E6F0D6A83E07ccD4A4da316e4d3;
// Euler supply token for wstETH (ewstETH)
address public constant EULER_ETOKEN_WSTETH = 0xbd1bd5C956684f7EB79DA40f582cbE1373A1D593;
// Euler supply token for USDC (eUSDC)
address public constant EULER_ETOKEN_USDC = 0xEb91861f8A4e1C12333F42DCE8fB0Ecdc28dA716;
// Euler debt token weth
address public constant EULER_DTOKEN_WETH = 0x62e28f054efc24b26A794F5C1249B6349454352C;
// address of the EULER rewards token contract
address public constant EULER_REWARDS_TOKEN = 0xd9Fcd98c322942075A5C3860693e9f4f03AAE07b;
// adress of the Chainlink aggregator for the USDC/eth price feed
address public constant CHAINLINK_USDC_ETH_PRICE_FEED = 0x986b5E1e1755e3C2440e960477f25201B0a8bbD4;
// Chainlink pricefeed (DAI -> ETH)
address public constant CHAINLINK_DAI_ETH_PRICE_FEED = 0x773616E4d11A78F511299002da57A0a94577F1f4;
// Chainlink pricefeed (stETH -> ETH)
address public constant CHAINLINK_STETH_ETH_PRICE_FEED = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812;
// Chainlink pricefeed (USDT -> ETH)
address public constant CHAINLINK_USDT_ETH_PRICE_FEED = 0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46;
// Liquity pricefeed (USD -> ETH) with Chainlink as primary and Tellor as backup.
address public constant LIQUITY_USD_ETH_PRICE_FEED = 0x4c517D4e2C851CA76d7eC94B805269Df0f2201De;
// address of the Balancer vault contract
address public constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
// Balancer admin account
address public constant BALANCER_ADMIN = 0x97207B095e4D5C9a6e4cfbfcd2C3358E03B90c4A;
// address of the Balance Protocol Fees Collector contract
address public constant BALANCER_FEES_COLLECTOR = 0xce88686553686DA562CE7Cea497CE749DA109f9F;
// address of the 0x swap router contract
address public constant ZERO_EX_ROUTER = 0xDef1C0ded9bec7F1a1670819833240f027b25EfF;
// address of Lifi swap router contract
address public constant LIFI = 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE;
// Compound v3
address public constant COMPOUND_V3_COMET_WETH = 0xA17581A9E3356d9A858b789D68B4d866e593aE94;
// Aave v2 lending pool
address public constant AAVE_V2_LENDING_POOL = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9;
// Aave v2 protocol data provider
address public constant AAVE_V2_PROTOCOL_DATA_PROVIDER = 0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d;
// Aave v2 interest bearing USDC (aUSDC) token
address public constant AAVE_V2_AUSDC_TOKEN = 0xBcca60bB61934080951369a648Fb03DF4F96263C;
// Aave v2 variable debt bearing WETH (variableDebtWETH) token
address public constant AAVE_V2_VAR_DEBT_WETH_TOKEN = 0xF63B34710400CAd3e044cFfDcAb00a0f32E33eCf;
// Liquity
address public constant LIQUITY_STABILITY_POOL = 0x66017D22b0f8556afDd19FC67041899Eb65a21bb;
address public constant LIQUITY_LQTY_TOKEN = 0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D;
// Morpho
address public constant MORPHO = 0x33333aea097c193e66081E930c33020272b33333;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 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);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// 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/transmissions11/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;
import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js.
pragma solidity ^0.8.20;
import {EnumerableSet} from "./EnumerableSet.sol";
/**
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableMap for EnumerableMap.UintToAddressMap;
*
* // Declare a set state variable
* EnumerableMap.UintToAddressMap private myMap;
* }
* ```
*
* The following map types are supported:
*
* - `uint256 -> address` (`UintToAddressMap`) since v3.0.0
* - `address -> uint256` (`AddressToUintMap`) since v4.6.0
* - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0
* - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0
* - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0
* - `uint256 -> bytes32` (`UintToBytes32Map`) since v5.1.0
* - `address -> address` (`AddressToAddressMap`) since v5.1.0
* - `address -> bytes32` (`AddressToBytes32Map`) since v5.1.0
* - `bytes32 -> address` (`Bytes32ToAddressMap`) since v5.1.0
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableMap.
* ====
*/
library EnumerableMap {
using EnumerableSet for EnumerableSet.Bytes32Set;
// To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
// and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit in bytes32.
/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentKey(bytes32 key);
struct Bytes32ToBytes32Map {
// Storage of keys
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 key => bytes32) _values;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
return map._keys.contains(key);
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
return map._keys.length();
}
/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
bytes32 key = map._keys.at(index);
return (key, map._values[key]);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
bytes32 value = map._values[key];
if (value == bytes32(0)) {
return (contains(map, key), bytes32(0));
} else {
return (true, value);
}
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
bytes32 value = map._values[key];
if (value == 0 && !contains(map, key)) {
revert EnumerableMapNonexistentKey(key);
}
return value;
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
return map._keys.values();
}
// UintToUintMap
struct UintToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToUintMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(key)));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintToAddressMap
struct UintToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToAddressMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, bytes32(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintToBytes32Map
struct UintToBytes32Map {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToBytes32Map storage map, uint256 key, bytes32 value) internal returns (bool) {
return set(map._inner, bytes32(key), value);
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToBytes32Map storage map, uint256 key) internal returns (bool) {
return remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToBytes32Map storage map, uint256 key) internal view returns (bool) {
return contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToBytes32Map storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToBytes32Map storage map, uint256 index) internal view returns (uint256, bytes32) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (uint256(key), value);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(UintToBytes32Map storage map, uint256 key) internal view returns (bool, bytes32) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(key));
return (success, value);
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToBytes32Map storage map, uint256 key) internal view returns (bytes32) {
return get(map._inner, bytes32(key));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(UintToBytes32Map storage map) internal view returns (uint256[] memory) {
bytes32[] memory store = keys(map._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToUintMap
struct AddressToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) {
return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToUintMap storage map, address key) internal returns (bool) {
return remove(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToUintMap storage map, address key) internal view returns (bool) {
return contains(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (address(uint160(uint256(key))), uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToUintMap storage map, address key) internal view returns (uint256) {
return uint256(get(map._inner, bytes32(uint256(uint160(key)))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToAddressMap
struct AddressToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToAddressMap storage map, address key, address value) internal returns (bool) {
return set(map._inner, bytes32(uint256(uint160(key))), bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToAddressMap storage map, address key) internal returns (bool) {
return remove(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToAddressMap storage map, address key) internal view returns (bool) {
return contains(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToAddressMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressToAddressMap storage map, uint256 index) internal view returns (address, address) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (address(uint160(uint256(key))), address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToAddressMap storage map, address key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToAddressMap storage map, address key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, bytes32(uint256(uint160(key)))))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToAddressMap storage map) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressToBytes32Map
struct AddressToBytes32Map {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(AddressToBytes32Map storage map, address key, bytes32 value) internal returns (bool) {
return set(map._inner, bytes32(uint256(uint160(key))), value);
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(AddressToBytes32Map storage map, address key) internal returns (bool) {
return remove(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(AddressToBytes32Map storage map, address key) internal view returns (bool) {
return contains(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(AddressToBytes32Map storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address, bytes32) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (address(uint160(uint256(key))), value);
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool, bytes32) {
(bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key))));
return (success, value);
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(AddressToBytes32Map storage map, address key) internal view returns (bytes32) {
return get(map._inner, bytes32(uint256(uint160(key))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(AddressToBytes32Map storage map) internal view returns (address[] memory) {
bytes32[] memory store = keys(map._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// Bytes32ToUintMap
struct Bytes32ToUintMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) {
return set(map._inner, key, bytes32(value));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) {
return remove(map._inner, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) {
return contains(map._inner, key);
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToUintMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (key, uint256(value));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) {
(bool success, bytes32 value) = tryGet(map._inner, key);
return (success, uint256(value));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) {
return uint256(get(map._inner, key));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
bytes32[] memory store = keys(map._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// Bytes32ToAddressMap
struct Bytes32ToAddressMap {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(Bytes32ToAddressMap storage map, bytes32 key, address value) internal returns (bool) {
return set(map._inner, key, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToAddressMap storage map, bytes32 key) internal returns (bool) {
return remove(map._inner, key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool) {
return contains(map._inner, key);
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(Bytes32ToAddressMap storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32ToAddressMap storage map, uint256 index) internal view returns (bytes32, address) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (key, address(uint160(uint256(value))));
}
/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool, address) {
(bool success, bytes32 value) = tryGet(map._inner, key);
return (success, address(uint160(uint256(value))));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (address) {
return address(uint160(uint256(get(map._inner, key))));
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToAddressMap storage map) internal view returns (bytes32[] memory) {
bytes32[] memory store = keys(map._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
}
// 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/transmissions11/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 MAX_UINT256 = 2**256 - 1;
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) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
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) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
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)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC-165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @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.
*/
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. This account bears the admin role (for the granted role).
* Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
*/
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 `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
/**
* @notice Interface for adapters that allow interactions with the lending protocols
*/
interface IAdapter {
/**
* @notice Returns the adapter's ID
*/
function id() external view returns (uint256);
/**
* @notice Sets the necessary approvals (allowances) for interacting with the lending protocol
*/
function setApprovals() external;
/**
* @notice Removes the given approvals (allowances) for interacting with the lending protocol
*/
function revokeApprovals() external;
/**
* @notice Supplies the given amount of collateral to the lending protocol
* @param amount The amount of collateral to supply
*/
function supply(uint256 amount) external;
/**
* @notice Borrows the given amount of debt from the lending protocol
* @param amount The amount of debt to borrow
*/
function borrow(uint256 amount) external;
/**
* @notice Repays the given amount of debt to the lending protocol
* @param amount The amount of debt to repay
*/
function repay(uint256 amount) external;
/**
* @notice Withdraws the given amount of collateral from the lending protocol
* @param amount The amount of collateral to withdraw
*/
function withdraw(uint256 amount) external;
/**
* @notice Claims rewards awarded by the lending protocol
* @param data Any data needed for the claim process
*/
function claimRewards(bytes calldata data) external;
/**
* @notice Returns the amount of collateral currently supplied to the lending protocol
* @param account The account to check
*/
function getCollateral(address account) external view returns (uint256);
/**
* @notice Returns the amount of debt currently borrowed from the lending protocol
* @param account The account to check
*/
function getDebt(address account) external view returns (uint256);
/**
* @notice Returns the maximum loan-to-value (LTV) ratio for the lending protocol
*/
function getMaxLtv() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* 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[ERC 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
pragma solidity ^0.8.0;
// Inspired by Aave Protocol's IFlashLoanReceiver.
interface IFlashLoanRecipient {
/**
* @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
*
* At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
* call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
* Vault, or else the entire flash loan will revert.
*
* `userData` is the same value passed in the `IVault.flashLoan` call.
*/
function receiveFlashLoan(
address[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
/// @title Price Converter Interface
/// @notice An empty placeholder interface for price converter contracts.
interface IPriceConverter {}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import {IPriceConverter} from "./IPriceConverter.sol";
/**
* @title Single Pair Price Converter Interface
* @notice Interface for price conversion between a specific asset and target token pair.
*/
interface ISinglePairPriceConverter is IPriceConverter {
/**
* @notice Returns the address of the asset token.
* @return The address of the asset token.
*/
function asset() external view returns (address);
/**
* @notice Returns the address of the target token.
* @return The address of the target token.
*/
function targetToken() external view returns (address);
/**
* @notice Converts an amount of target token to the equivalent amount of asset.
* @param _tokenAmount The amount of target token to convert.
* @return assetAmount The equivalent amount of the asset.
*/
function targetTokenToAsset(uint256 _tokenAmount) external view returns (uint256 assetAmount);
/**
* @notice Converts an amount of asset to the equivalent amount of target token.
* @param _assetAmount The amount of asset to convert.
* @return tokenAmount The equivalent amount of the target token.
*/
function assetToTargetToken(uint256 _assetAmount) external view returns (uint256 tokenAmount);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import "./ISwapper.sol";
/**
* @title ISinglePairSwapper
* @notice Interface for a swapper handling swaps between a specific asset and target token pair.
*/
interface ISinglePairSwapper is ISwapper {
/**
* @notice Returns the address of the asset token.
* @return The address of the asset token.
*/
function asset() external view returns (address);
/**
* @notice Returns the address of the target token.
* @return The address of the target token.
*/
function targetToken() external view returns (address);
/**
* @notice Swaps the target token for the asset.
* @param _targetAmount The amount of the target token to swap.
* @param _assetAmountOutMin The minimum amount of the asset to receive.
* @return amountReceived The amount of the asset received from the swap.
*/
function swapTargetTokenForAsset(uint256 _targetAmount, uint256 _assetAmountOutMin)
external
returns (uint256 amountReceived);
/**
* @notice Swaps the asset for an exact amount of the target token.
* @param _targetTokenAmountOut The exact amount of the target token desired.
* @return amountSpent The amount of the asset spent to receive the target token.
*/
function swapAssetForExactTargetToken(uint256 _targetTokenAmountOut) external returns (uint256 amountSpent);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
/**
* @title ISwapper
* @notice Interface for a token swapper contract.
*/
interface ISwapper {
/**
* @notice Returns the address of the swap router used by the swapper.
* @return The address of the swap router.
*/
function swapRouter() external view returns (address);
/**
* @notice Swaps tokens using the swapper's router.
* @param _tokenIn The address of the token to swap from.
* @param _tokenOut The address of the token to swap to.
* @param _amountIn The amount of `_tokenIn` to swap.
* @param _amountOutMin The minimum amount of `_tokenOut` to receive.
* @param _swapData Arbitrary data required by the swap router.
* @return The amount of `_tokenOut` received from the swap.
*/
function swapTokens(
address _tokenIn,
address _tokenOut,
uint256 _amountIn,
uint256 _amountOutMin,
bytes calldata _swapData
) external returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IVault {
/**
* @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,
* and then reverting unless the tokens plus a proportional protocol fee have been returned.
*
* The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount
* for each token contract. `tokens` must be sorted in ascending order.
*
* The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the
* `receiveFlashLoan` call.
*
* Emits `FlashLoan` events.
*/
function flashLoan(address recipient, address[] memory tokens, uint256[] memory amounts, bytes memory userData)
external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.13;
/**
* Library containing the addresses for all the deployed contracts on Ethereum Mainnet
*/
library MainnetAddresses {
address public constant QUARTZ = 0xbA8A621b4a54e61C442F5Ec623687e2a942225ef;
address public constant SCWETHV2 = 0x4c406C068106375724275Cbff028770C544a1333;
address public constant SCWETHV2_MORPHO_ADAPTER = 0x4420F0E6A38863330FD4885d76e1265DAD5aa9df;
address public constant SCWETHV2_COMPOUND_ADAPTER = 0x379022F4d2619c7fbB95f9005ea0897e3a31a0C4;
address public constant SCWETHV2_AAVEV3_ADAPTER = 0x99C55166Dc78a96C52caf1dB201C0eB0086fB83E;
address public constant SCUSDCV2 = 0x096697720056886b905D0DEB0f06AfFB8e4665E5;
address public constant SCUSDCV2_MORPHO_ADAPTER = 0x92803F0E528c3F5053A1aBF1f0f2AeC45751a189;
address public constant SCUSDCV2_AAVEV2_ADAPTER = 0xE0E9E98FD963C2e69718C76939924522A9646885;
address public constant SCUSDCV2_AAVEV3_ADAPTER = 0xf59c324fF111D86894f175E22B70b0d54998ff3E;
address public constant SCDAI = address(0x00); //todo: update after deployment
address public constant SCDAI_SPARK_ADAPTER = address(0x00); //TODO: update after deployment
address public constant PRICE_CONVERTER = 0xD76B0Ff4A487CaFE4E19ed15B73f12f6A92095Ca;
address public constant SWAPPER = 0x6649f12b5ef495a3861b21E3206B1AbfA33A6531;
address public constant KEEPER = 0x397502F15E11C524F23C0c003f5E8004C1c5c71D;
address public constant MULTISIG = 0x6cF38285FdFAf8D67205ca444A899025b5B18e83;
// TODO: TREASURY == MULTISIG for now, change to the staking contract address once it's deployed
address public constant TREASURY = MULTISIG;
// used on some earlier deploys
address public constant OLD_MULTISIG = 0x035F210e5d14054E8AE5A6CFA76d643aA200D56E;
address public constant OLD_KEEPER = 0x06444B9F0c6a966b8B9Bc1e808d2B165a87e3a38;
}
// 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/transmissions11/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;
/// @solidity memory-safe-assembly
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;
/// @solidity memory-safe-assembly
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;
/// @solidity memory-safe-assembly
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;
/// @solidity memory-safe-assembly
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: AGPL-3.0-only
pragma solidity ^0.8.10;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {AccessControl} from "openzeppelin-contracts/access/AccessControl.sol";
import {Constants as C} from "./lib/Constants.sol";
import {
CallerNotAdmin,
CallerNotKeeper,
ZeroAddress,
InvalidFlashLoanCaller,
TreasuryCannotBeZero,
FeesTooHigh,
InvalidFloatPercentage,
InvalidSlippageTolerance
} from "./errors/scErrors.sol";
abstract contract sc4626 is ERC4626, AccessControl {
constructor(address _admin, address _keeper, ERC20 _asset, string memory _name, string memory _symbol)
ERC4626(_asset, _name, _symbol)
{
if (_admin == address(0)) revert ZeroAddress();
if (_keeper == address(0)) revert ZeroAddress();
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
_grantRole(KEEPER_ROLE, _keeper);
}
event TreasuryUpdated(address indexed user, address newTreasury);
event PerformanceFeeUpdated(address indexed user, uint256 newPerformanceFee);
event FloatPercentageUpdated(address indexed user, uint256 newFloatPercentage);
event SlippageToleranceUpdated(address indexed admin, uint256 newSlippageTolerance);
/// Role allowed to harvest/reinvest
bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
// flag for checking flash loan caller
bool public flashLoanInitiated;
// address of the treasury to send performance fees to
address public treasury;
// performance fee percentage
uint256 public performanceFee = 0.1e18; // 10%
// percentage of the total assets to be kept in the vault as a withdrawal buffer
uint256 public floatPercentage = 0.01e18;
// max slippage tolerance for swaps
uint256 public slippageTolerance = 0.99e18; // 1% default
/// @notice set the treasury address
/// @param _newTreasury the new treasury address
function setTreasury(address _newTreasury) external {
_onlyAdmin();
if (_newTreasury == address(0)) revert TreasuryCannotBeZero();
treasury = _newTreasury;
emit TreasuryUpdated(msg.sender, _newTreasury);
}
/// @notice set the performance fee percentage
/// @param _newPerformanceFee the new performance fee percentage
/// @dev performance fee is a number between 0 and 1e18
function setPerformanceFee(uint256 _newPerformanceFee) external {
_onlyAdmin();
if (_newPerformanceFee > 1e18) revert FeesTooHigh();
performanceFee = _newPerformanceFee;
emit PerformanceFeeUpdated(msg.sender, _newPerformanceFee);
}
/**
* @notice Set the percentage of the total assets to be kept in the vault as a withdrawal buffer.
* @param _newFloatPercentage The new float percentage value.
*/
function setFloatPercentage(uint256 _newFloatPercentage) external {
_onlyAdmin();
if (_newFloatPercentage > C.ONE) revert InvalidFloatPercentage();
floatPercentage = _newFloatPercentage;
emit FloatPercentageUpdated(msg.sender, _newFloatPercentage);
}
/**
* @notice Set the default slippage tolerance for swapping tokens.
* @param _newSlippageTolerance The new slippage tolerance value.
*/
function setSlippageTolerance(uint256 _newSlippageTolerance) external {
_onlyAdmin();
if (_newSlippageTolerance > C.ONE) revert InvalidSlippageTolerance();
slippageTolerance = _newSlippageTolerance;
emit SlippageToleranceUpdated(msg.sender, _newSlippageTolerance);
}
function _onlyAdmin() internal view {
if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert CallerNotAdmin();
}
function _onlyKeeper() internal view {
if (!hasRole(KEEPER_ROLE, msg.sender)) revert CallerNotKeeper();
}
function _onlyKeeperOrFlashLoan() internal view {
if (!flashLoanInitiated) _onlyKeeper();
}
function _initiateFlashLoan() internal {
flashLoanInitiated = true;
}
function _finalizeFlashLoan() internal {
flashLoanInitiated = false;
}
function _isFlashLoanInitiated() internal view {
if (!flashLoanInitiated) revert InvalidFlashLoanCaller();
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import {
NoProfitsToSell,
FlashLoanAmountZero,
EndAssetBalanceTooLow,
FloatBalanceTooLow,
TargetTokenMismatch,
InvestedAmountNotWithdrawn
} from "../errors/scErrors.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";
import {EnumerableMap} from "openzeppelin-contracts/utils/structs/EnumerableMap.sol";
import {Constants as C} from "../lib/Constants.sol";
import {BaseV2Vault} from "./BaseV2Vault.sol";
import {IAdapter} from "./IAdapter.sol";
import {ISinglePairPriceConverter} from "./priceConverter/ISinglePairPriceConverter.sol";
import {ISinglePairSwapper} from "./swapper/ISinglePairSwapper.sol";
/**
* @title scCrossAssetYieldVault
* @notice An abstract vault contract implementing cross-asset yield strategies.
* @dev Cross-asset means that the yield generated in the target vault (target tokens) is converted to the underlying asset token of the vault.
* @dev Inherits from BaseV2Vault and provides functionalities to interact with multiple lending markets.
*/
abstract contract scCrossAssetYieldVault is BaseV2Vault {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
using Address for address;
using EnumerableMap for EnumerableMap.UintToAddressMap;
enum FlashLoanType {
Reallocate,
ExitAllPositions
}
event EmergencyExitExecuted(
address indexed admin, uint256 targetTokenWithdrawn, uint256 debtRepaid, uint256 collateralReleased
);
event Reallocated();
event Rebalanced(uint256 totalCollateral, uint256 totalDebt, uint256 floatBalance);
event ProfitSold(uint256 targetTokenSold, uint256 assetReceived);
event Supplied(uint256 adapterId, uint256 amount);
event Borrowed(uint256 adapterId, uint256 amount);
event Repaid(uint256 adapterId, uint256 amount);
event Withdrawn(uint256 adapterId, uint256 amount);
event Invested(uint256 targetTokenAmount);
event Disinvested(uint256 targetTokenAmount);
event TargetVaultUpdated(address targetVault);
/// @notice The target token used as underlying in the target vault.
ERC20 public immutable targetToken;
/// @notice The target vault (staking vault) where target tokens are invested.
ERC4626 public targetVault;
constructor(
address _admin,
address _keeper,
ERC20 _asset,
ERC4626 _targetVault,
ISinglePairPriceConverter _priceConverter,
ISinglePairSwapper _swapper,
string memory _name,
string memory _symbol
) BaseV2Vault(_admin, _keeper, _asset, _priceConverter, _swapper, _name, _symbol) {
_zeroAddressCheck(address(_targetVault));
targetVault = _targetVault;
targetToken = targetVault.asset();
targetToken.safeApprove(address(_targetVault), type(uint256).max);
}
/*//////////////////////////////////////////////////////////////
PUBLIC API
//////////////////////////////////////////////////////////////*/
/**
* @notice Updates the address of the target vault.
* @dev Reverts if invested amount is not zero or if the target token (underlying asset) is different.
* @param _newTargetVault address of the new target vault.
*/
function updateTargetVault(ERC4626 _newTargetVault) external {
_onlyAdmin();
if (_newTargetVault.asset() != targetToken) revert TargetTokenMismatch();
if (targetTokenInvestedAmount() != 0) revert InvestedAmountNotWithdrawn();
targetVault = _newTargetVault;
emit TargetVaultUpdated(address(_newTargetVault));
}
/**
* @notice Rebalance the vault's positions and loans across multiple lending markets.
* @dev Called to adjust the target token debt, maintain the desired LTV and avoid liquidation.
* @param _callData An array of encoded function calls to be executed.
*/
function rebalance(bytes[] calldata _callData) external {
_onlyKeeper();
_multiCall(_callData);
// Invest any remaining target token amount after rebalancing
_invest();
// Enforce float to be above the minimum required
uint256 float = assetBalance();
uint256 floatRequired = totalAssets().mulWadDown(floatPercentage);
if (float < floatRequired) {
revert FloatBalanceTooLow(float, floatRequired);
}
emit Rebalanced(totalCollateral(), totalDebt(), float);
}
/**
* @notice Reallocate collateral and debt between lending markets.
* @dev Uses flash loans to repay debt and release collateral in one market to move to another.
* @param _flashLoanAmount The amount of target tokens to flash loan.
* @param _callData An array of encoded function calls to be executed.
*/
function reallocate(uint256 _flashLoanAmount, bytes[] calldata _callData) external {
_onlyKeeper();
if (_flashLoanAmount == 0) revert FlashLoanAmountZero();
address[] memory tokens = new address[](1);
tokens[0] = address(targetToken);
uint256[] memory amounts = new uint256[](1);
amounts[0] = _flashLoanAmount;
_initiateFlashLoan();
balancerVault.flashLoan(address(this), tokens, amounts, abi.encode(FlashLoanType.Reallocate, _callData));
_finalizeFlashLoan();
emit Reallocated();
}
/**
* @notice Sells profits (in taget tokens) by swapping to the asset token.
* @dev The vault generates yield in target tokens; profits are sold to asse tokenst.
* @param _assetAmountOutMin The minimum amount of asset tokens to receive.
*/
function sellProfit(uint256 _assetAmountOutMin) external {
_onlyKeeper();
uint256 profit = _calculateProfitInTargetToken(targetTokenInvestedAmount(), totalDebt());
if (profit == 0) revert NoProfitsToSell();
uint256 withdrawn = _disinvest(profit);
uint256 assetReceived = _swapTargetTokenForAsset(withdrawn, _assetAmountOutMin);
emit ProfitSold(withdrawn, assetReceived);
}
/**
* @notice Emergency exit to disinvest everything, repay all debt, and withdraw all collateral.
* @dev Closes all positions to release assets and realize any losses.
* @param _endAssetBalanceMin The minimum asset balance expected after execution.
*/
function exitAllPositions(uint256 _endAssetBalanceMin) external {
_onlyKeeper();
uint256 collateral = totalCollateral();
uint256 debt = totalDebt();
uint256 targetTokenBalance =
targetVault.redeem(targetVault.balanceOf(address(this)), address(this), address(this));
if (debt > targetTokenBalance) {
// not enough target tokens to repay all debt, flashloan the difference
address[] memory tokens = new address[](1);
tokens[0] = address(targetToken);
uint256[] memory amounts = new uint256[](1);
amounts[0] = debt - targetTokenBalance;
_initiateFlashLoan();
balancerVault.flashLoan(address(this), tokens, amounts, abi.encode(FlashLoanType.ExitAllPositions));
_finalizeFlashLoan();
} else {
_repayAllDebtAndWithdrawCollateral();
// Swap remaining target tokens to asset if any
uint256 targetTokenLeft = _targetTokenBalance();
if (targetTokenLeft != 0) _swapTargetTokenForAsset(targetTokenLeft, 0);
}
if (assetBalance() < _endAssetBalanceMin) revert EndAssetBalanceTooLow();
emit EmergencyExitExecuted(msg.sender, targetTokenBalance, debt, collateral);
}
/**
* @notice Handles flashloan callbacks.
* @dev Called by Balancer's vault in 2 situations:
* 1. When the vault is underwater and the vault needs to exit all positions.
* 2. When the vault needs to reallocate capital between lending markets.
* @param _amounts single elment array containing the amount of target tokens being flashloaned.
* @param _data The encoded data that was passed to the flashloan.
*/
function receiveFlashLoan(
address[] calldata,
uint256[] calldata _amounts,
uint256[] calldata _feeAmounts,
bytes calldata _data
) external {
_isFlashLoanInitiated();
uint256 flashLoanAmount = _amounts[0];
FlashLoanType flashLoanType = abi.decode(_data, (FlashLoanType));
if (flashLoanType == FlashLoanType.ExitAllPositions) {
_repayAllDebtAndWithdrawCollateral();
_swapAssetForExactTargetToken(flashLoanAmount);
} else {
(, bytes[] memory callData) = abi.decode(_data, (FlashLoanType, bytes[]));
_multiCall(callData);
}
targetToken.safeTransfer(address(balancerVault), flashLoanAmount + _feeAmounts[0]);
}
/**
* @notice Supply asset tokens to a lending market.
* @param _adapterId The ID of the lending market adapter.
* @param _amount The amount of asset tokens to supply.
*/
function supply(uint256 _adapterId, uint256 _amount) external {
_onlyKeeperOrFlashLoan();
_isSupportedCheck(_adapterId);
_supply(_adapterId, _amount);
}
/**
* @notice Borrow an amount of target tokens from a lending market.
* @param _adapterId The ID of the lending market adapter.
* @param _amount The amount of target tokens to borrow.
*/
function borrow(uint256 _adapterId, uint256 _amount) external {
_onlyKeeperOrFlashLoan();
_isSupportedCheck(_adapterId);
_borrow(_adapterId, _amount);
}
/**
* @notice Repay an amount of debt to a lending market.
* @param _adapterId The ID of the lending market adapter.
* @param _amount The amount of target tokens to repay.
*/
function repay(uint256 _adapterId, uint256 _amount) external {
_onlyKeeperOrFlashLoan();
_isSupportedCheck(_adapterId);
_repay(_adapterId, _amount);
}
/**
* @notice Withdraw asset tokens from a lending market.
* @param _adapterId The ID of the lending market adapter.
* @param _amount The amount of asset tokens to withdraw.
*/
function withdraw(uint256 _adapterId, uint256 _amount) external {
_onlyKeeperOrFlashLoan();
_isSupportedCheck(_adapterId);
_withdraw(_adapterId, _amount);
}
/**
* @notice Withdraw target tokens from the target vault.
* @param _amount The amount of target tokens to withdraw.
*/
function disinvest(uint256 _amount) external {
_onlyKeeper();
_disinvest(_amount);
}
/**
* @notice Returns the total claimable assets of the vault in asset tokens.
* @return The total assets managed by the vault.
*/
function totalAssets() public view override returns (uint256) {
return _calculateTotalAssets(assetBalance(), totalCollateral(), targetTokenInvestedAmount(), totalDebt());
}
/**
* @notice Returns the asset balance of the vault.
* @return The balance of asset tokens held by the vault.
*/
function assetBalance() public view returns (uint256) {
return asset.balanceOf(address(this));
}
/**
* @notice Returns the amount of asset tokens supplied as collateral in a lending market.
* @param _adapterId The ID of the lending market adapter.
* @return The amount of collateral supplied in the specified lending market.
*/
function getCollateral(uint256 _adapterId) external view returns (uint256) {
if (!isSupported(_adapterId)) return 0;
return IAdapter(protocolAdapters.get(_adapterId)).getCollateral(address(this));
}
/**
* @notice Returns the total amount of asset tokens supplied as collateral in all lending markets.
* @return total The total collateral across all lending markets.
*/
function totalCollateral() public view returns (uint256 total) {
uint256 length = protocolAdapters.length();
for (uint256 i = 0; i < length; i++) {
(, address adapter) = protocolAdapters.at(i);
total += IAdapter(adapter).getCollateral(address(this));
}
}
/**
* @notice Returns the amount of target tokens borrowed from a lending market.
* @param _adapterId The ID of the lending market adapter.
* @return The amount of debt in target tokens for the specified lending market.
*/
function getDebt(uint256 _adapterId) external view returns (uint256) {
if (!isSupported(_adapterId)) return 0;
return IAdapter(protocolAdapters.get(_adapterId)).getDebt(address(this));
}
/**
* @notice Returns the total amount of target tokens borrowed across all lending markets.
* @return total The total debt in target tokens across all lending markets.
*/
function totalDebt() public view returns (uint256 total) {
uint256 length = protocolAdapters.length();
for (uint256 i = 0; i < length; i++) {
(, address adapter) = protocolAdapters.at(i);
total += IAdapter(adapter).getDebt(address(this));
}
}
/**
* @notice Returns the amount of target tokens invested (staked) in the target vault.
* @return The amount of target tokens invested in the target vault.
*/
function targetTokenInvestedAmount() public view returns (uint256) {
return targetVault.convertToAssets(targetVault.balanceOf(address(this)));
}
/**
* @notice Returns the amount of profit (in target tokens) made by the vault.
* @dev Profit is calculated as the difference between invested and owed target token amounts.
* @return The amount of profit in target tokens.
*/
function getProfit() public view returns (uint256) {
return _calculateProfitInTargetToken(targetTokenInvestedAmount(), totalDebt());
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Supplies asset tokens to a lending market adapter.
* @param _adapterId The ID of the adapter.
* @param _amount The amount of asset tokens to supply.
*/
function _supply(uint256 _adapterId, uint256 _amount) internal {
_adapterDelegateCall(_adapterId, abi.encodeWithSelector(IAdapter.supply.selector, _amount));
emit Supplied(_adapterId, _amount);
}
/**
* @notice Borrows target tokens from a lending market adapter.
* @param _adapterId The ID of the adapter.
* @param _amount The amount of target tokens to borrow.
*/
function _borrow(uint256 _adapterId, uint256 _amount) internal {
_adapterDelegateCall(_adapterId, abi.encodeWithSelector(IAdapter.borrow.selector, _amount));
emit Borrowed(_adapterId, _amount);
}
/**
* @notice Repays debt in target tokens to a lending market adapter.
* @param _adapterId The ID of the adapter.
* @param _amount The amount of debt to repay.
*/
function _repay(uint256 _adapterId, uint256 _amount) internal {
uint256 targetTokenBalance = _targetTokenBalance();
_amount = _amount > targetTokenBalance ? targetTokenBalance : _amount;
_adapterDelegateCall(_adapterId, abi.encodeWithSelector(IAdapter.repay.selector, _amount));
emit Repaid(_adapterId, _amount);
}
/**
* @notice Withdraws asset tokens from a lending market adapter.
* @param _adapterId The ID of the adapter.
* @param _amount The amount of asset tokens to withdraw.
*/
function _withdraw(uint256 _adapterId, uint256 _amount) internal {
_adapterDelegateCall(_adapterId, abi.encodeWithSelector(IAdapter.withdraw.selector, _amount));
emit Withdrawn(_adapterId, _amount);
}
/**
* @notice Invests any available target tokens into the target vault.
*/
function _invest() internal {
uint256 targetTokenBalance = _targetTokenBalance();
if (targetTokenBalance > 0) {
targetVault.deposit(targetTokenBalance, address(this));
emit Invested(targetTokenBalance);
}
}
/**
* @notice Disinvests (withdraws) target tokens from the target vault.
* @param _targetTokenAmount The amount of target tokens to disinvest.
* @return The amount of target tokens withdrawn.
*/
function _disinvest(uint256 _targetTokenAmount) internal returns (uint256) {
uint256 shares = targetVault.convertToShares(_targetTokenAmount);
uint256 amount = targetVault.redeem(shares, address(this), address(this));
emit Disinvested(amount);
return amount;
}
/**
* @notice Repays all debt and withdraws all collateral from all lending markets.
*/
function _repayAllDebtAndWithdrawCollateral() internal {
uint256 length = protocolAdapters.length();
for (uint256 i = 0; i < length; i++) {
(uint256 id, address adapter) = protocolAdapters.at(i);
uint256 debt = IAdapter(adapter).getDebt(address(this));
uint256 collateral = IAdapter(adapter).getCollateral(address(this));
if (debt > 0) _repay(id, debt);
if (collateral > 0) _withdraw(id, collateral);
}
}
/**
* @notice Hook called before withdrawing assets.
* @param _assets The amount of assets to withdraw.
*/
function beforeWithdraw(uint256 _assets, uint256) internal override {
uint256 initialBalance = assetBalance();
if (initialBalance >= _assets) return;
uint256 collateral = totalCollateral();
uint256 debt = totalDebt();
uint256 invested = targetTokenInvestedAmount();
uint256 total = _calculateTotalAssets(initialBalance, collateral, invested, debt);
uint256 profit = _calculateProfitInTargetToken(invested, debt);
uint256 floatRequired = total > _assets ? (total - _assets).mulWadUp(floatPercentage) : 0;
uint256 assetNeeded = _assets + floatRequired - initialBalance;
// first try to sell profits to cover withdrawal amount
if (profit != 0) {
uint256 withdrawn = _disinvest(profit);
uint256 assetAmountOutMin = converter().targetTokenToAsset(withdrawn).mulWadDown(slippageTolerance);
uint256 assetReceived = _swapTargetTokenForAsset(withdrawn, assetAmountOutMin);
if (initialBalance + assetReceived >= _assets) return;
assetNeeded -= assetReceived;
}
// if we still need more asset, we need to repay debt and withdraw collateral
_repayDebtAndReleaseCollateral(debt, collateral, invested, assetNeeded);
}
/**
* @notice Repays debt and releases collateral to meet asset needs.
* @param _totalDebt The total debt owed.
* @param _totalCollateral The total collateral supplied.
* @param _invested The total invested in the target vault.
* @param _assetNeeded The amount of asset tokens needed.
*/
function _repayDebtAndReleaseCollateral(
uint256 _totalDebt,
uint256 _totalCollateral,
uint256 _invested,
uint256 _assetNeeded
) internal {
// handle rounding errors when withdrawing everything
_assetNeeded = _assetNeeded > _totalCollateral ? _totalCollateral : _assetNeeded;
// to keep the same ltv, total debt in targetToken to be repaid has to be proportional to total asset collateral we are withdrawing
uint256 targetTokenNeeded = _assetNeeded.mulDivUp(_totalDebt, _totalCollateral);
targetTokenNeeded = targetTokenNeeded > _invested ? _invested : targetTokenNeeded;
uint256 targetTokenDisinvested = 0;
if (targetTokenNeeded != 0) targetTokenDisinvested = _disinvest(targetTokenNeeded);
// Repay debt and withdraw collateral proportionally from each protocol
uint256 length = protocolAdapters.length();
for (uint256 i = 0; i < length; i++) {
(uint256 id, address adapter) = protocolAdapters.at(i);
uint256 collateral = IAdapter(adapter).getCollateral(address(this));
if (collateral == 0) continue;
uint256 debt = IAdapter(adapter).getDebt(address(this));
uint256 toWithdraw = _assetNeeded.mulDivUp(collateral, _totalCollateral);
if (targetTokenDisinvested != 0 && debt != 0) {
// Keep the same LTV when withdrawing collateral
uint256 toRepay = toWithdraw.mulDivUp(debt, collateral);
if (toRepay > targetTokenDisinvested) {
toRepay = targetTokenDisinvested;
} else {
targetTokenDisinvested -= toRepay;
}
_repay(id, toRepay);
}
_withdraw(id, toWithdraw);
}
}
/**
* @notice Calculates the total assets of the vault.
* @param _float The current float balance.
* @param _collateral The total collateral supplied.
* @param _invested The total invested in the target vault.
* @param _debt The total debt owed.
* @return total The total assets of the vault.
*/
function _calculateTotalAssets(uint256 _float, uint256 _collateral, uint256 _invested, uint256 _debt)
internal
view
returns (uint256 total)
{
total = _float + _collateral;
uint256 profit = _calculateProfitInTargetToken(_invested, _debt);
if (profit != 0) {
// account for slippage when selling targetToken profits
total += converter().targetTokenToAsset(profit).mulWadDown(slippageTolerance);
} else {
total -= converter().targetTokenToAsset(_debt - _invested);
}
}
/**
* @notice Calculates the profit in target tokens.
* @param _invested The amount invested in the target vault.
* @param _debt The total debt owed.
* @return The profit in target tokens.
*/
function _calculateProfitInTargetToken(uint256 _invested, uint256 _debt) internal pure returns (uint256) {
return _invested > _debt ? _invested - _debt : 0;
}
/**
* @notice Returns the target token balance of the vault.
* @return The balance of target tokens held by the vault.
*/
function _targetTokenBalance() internal view returns (uint256) {
return targetToken.balanceOf(address(this));
}
/**
* @notice Returns the price converter contract casted to ISinglePairPriceConverter.
* @return The price converter contract.
*/
function converter() public view returns (ISinglePairPriceConverter) {
return ISinglePairPriceConverter(address(priceConverter));
}
/**
* @notice Swaps target tokens for asset tokens using the swapper contract.
* @param _targetTokenAmount The amount of target tokens to swap.
* @param _assetAmountOutMin The minimum amount of asset tokens to receive.
* @return The amount of asset tokens received.
*/
function _swapTargetTokenForAsset(uint256 _targetTokenAmount, uint256 _assetAmountOutMin)
internal
virtual
returns (uint256)
{
bytes memory result = address(swapper).functionDelegateCall(
abi.encodeCall(ISinglePairSwapper.swapTargetTokenForAsset, (_targetTokenAmount, _assetAmountOutMin))
);
return abi.decode(result, (uint256));
}
/**
* @notice Swaps asset tokens for an exact amount of target tokens using the swapper contract.
* @param _targetTokenAmountOut The exact amount of target tokens desired.
*/
function _swapAssetForExactTargetToken(uint256 _targetTokenAmountOut) internal virtual {
address(swapper).functionDelegateCall(
abi.encodeCall(ISinglePairSwapper.swapAssetForExactTargetToken, (_targetTokenAmountOut))
);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {Constants as C} from "../lib/Constants.sol";
import {scSDAI} from "./scSDAI.sol";
/**
* @title scDAI
* @notice Sandclock DAI Vault - A wrapper ERC4626 contract to support DAI deposits and withdrawals on scSDAI.
* @dev This contract allows users to deposit DAI and interact with the scSDAI vault seamlessly.
*/
contract scDAI is ERC4626 {
using SafeTransferLib for ERC20;
using SafeTransferLib for ERC4626;
/// @notice The DAI ERC20 token contract.
ERC20 public constant dai = ERC20(C.DAI);
/// @notice The sDAI ERC4626 token contract.
ERC4626 public constant sDai = ERC4626(C.SDAI);
/// @notice The scSDAI ERC4626 vault contract.
ERC4626 public immutable scsDai;
constructor(ERC4626 _scsDAI) ERC4626(dai, "Sandclock Yield DAI", "scDAI") {
scsDai = _scsDAI;
dai.safeApprove(C.SDAI, type(uint256).max);
sDai.safeApprove(address(_scsDAI), type(uint256).max);
}
/**
* @notice Returns the total amount of underlying assets held by the vault.
* @return The total assets in DAI.
*/
function totalAssets() public view override returns (uint256) {
// Balance in sDAI
uint256 balance = scsDai.convertToAssets(scsDai.balanceOf(address(this)));
return sDai.convertToAssets(balance);
}
/**
* @notice Hook called after a deposit is made.
* @param assets The amount of assets deposited.
*/
function afterDeposit(uint256 assets, uint256) internal override {
// DAI => sDAI
assets = sDai.deposit(assets, address(this));
// Deposit sDAI to scSDAI
scsDai.deposit(assets, address(this));
}
/**
* @notice Withdraws assets from the vault.
* @param assets The amount of assets to withdraw.
* @param receiver The address to receive the withdrawn assets.
* @param owner The address of the owner of the shares.
* @return shares The amount of shares burned.
*/
function withdraw(uint256 assets, address receiver, address owner) public override returns (uint256 shares) {
// NOTE: copied and modified from ERC4626.sol with the highlighted changes below
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// change1: removed "beforeWithdraw(assets, shares);" here as it is not needed
_burn(owner, shares);
// change2: removed "asset.safeTransfer(receiver, assets);" and replaced with "_withdrawDaiFromScSDai(...)" call
assets = _withdrawDaiFromScSDai(assets, shares, receiver);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}
/**
* @notice Redeems shares for assets.
* @param shares The amount of shares to redeem.
* @param receiver The address to receive the withdrawn assets.
* @param owner The address of the owner of the shares.
* @return assets The amount of assets withdrawn.
*/
function redeem(uint256 shares, address receiver, address owner) public override returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
_burn(owner, shares);
assets = _withdrawDaiFromScSDai(assets, shares, receiver);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}
/**
* @notice Internal function to withdraw DAI from scSDAI.
* @param daiAmount The amount of DAI to withdraw.
* @param receiver The address to receive the withdrawn DAI.
* @return The amount of DAI withdrawn.
*/
function _withdrawDaiFromScSDai(uint256 daiAmount, uint256, address receiver) internal returns (uint256) {
uint256 sDaiAmount = sDai.convertToShares(daiAmount);
scsDai.withdraw(sDaiAmount, address(this), address(this));
// redeem sDAI for DAI
return sDai.redeem(sDaiAmount, receiver, address(this));
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.10;
error InvalidTargetLtv();
error InvalidMaxLtv();
error InvalidFlashLoanCaller();
error InvalidSlippageTolerance();
error InvalidFloatPercentage();
error ZeroAddress();
error PleaseUseRedeemMethod();
error FeesTooHigh();
error TreasuryCannotBeZero();
error VaultNotUnderwater();
error CallerNotAdmin();
error CallerNotKeeper();
error NoProfitsToSell();
error EndUsdcBalanceTooLow();
error EndDaiBalanceTooLow();
error EndAssetBalanceTooLow();
error InsufficientDepositBalance();
error AmountReceivedBelowMin();
error FlashLoanAmountZero();
error ProtocolNotSupported(uint256 adapterId);
error ProtocolInUse(uint256 adapterId);
error FloatBalanceTooLow(uint256 actual, uint256 required);
error TokenOutNotAllowed(address token);
error TargetTokenMismatch();
error InvestedAmountNotWithdrawn();
library Check {
function isZeroAddress(address _address) internal pure {
if (_address == address(0)) revert ZeroAddress();
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {Constants as C} from "../lib/Constants.sol";
import {scCrossAssetYieldVault} from "./scCrossAssetYieldVault.sol";
import {ISinglePairPriceConverter} from "./priceConverter/ISinglePairPriceConverter.sol";
import {ISinglePairSwapper} from "./swapper/ISinglePairSwapper.sol";
import {MainnetAddresses as MA} from "../../script/base/MainnetAddresses.sol";
/**
* @title scSDAI
* @notice Sandclock USDT Vault implementation.
* @dev Inherits from scCrossAssetYieldVault to manage and generate sDAI yield.
*/
contract scSDAI is scCrossAssetYieldVault {
using SafeTransferLib for ERC20;
constructor(
address _admin,
address _keeper,
ERC4626 _targetVault,
ISinglePairPriceConverter _priceConverter,
ISinglePairSwapper _swapper
)
scCrossAssetYieldVault(
_admin,
_keeper,
ERC20(C.SDAI),
_targetVault,
_priceConverter,
_swapper,
"Sandclock SDAI Vault",
"scSDAI"
)
{
ERC20(C.DAI).safeApprove(address(asset), type(uint256).max);
}
}
{
"compilationTarget": {
"src/steth/scDAI.sol": "scDAI"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000000
},
"remappings": [
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":aave-v3-core/=lib/aave-v3-core/",
":aave-v3/=lib/aave-v3-core/contracts/",
":create3-factory/=lib/create3-factory/src/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":euler-interfaces-legacy/=lib/euler-interfaces-legacy/contracts/",
":forge-std/=lib/forge-std/src/",
":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
":solidity-stringutils/=lib/surl/lib/solidity-stringutils/",
":solmate/=lib/solmate/src/",
":surl/=lib/surl/src/"
]
}
[{"inputs":[{"internalType":"contract ERC4626","name":"_scsDAI","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dai","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sDai","outputs":[{"internalType":"contract ERC4626","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"scsDai","outputs":[{"internalType":"contract ERC4626","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]