Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
AaveDebtTokenAdaptor
Compiler Version
v0.8.16+commit.07a7930e
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { BaseAdaptor, ERC20, SafeTransferLib, Cellar, SwapRouter, Registry } from "src/modules/adaptors/BaseAdaptor.sol";
import { IPool } from "src/interfaces/external/IPool.sol";
import { IAaveToken } from "src/interfaces/external/IAaveToken.sol";
/**
* @title Aave debtToken Adaptor
* @notice Allows Cellars to interact with Aave debtToken positions.
* @author crispymangoes
*/
contract AaveDebtTokenAdaptor is BaseAdaptor {
using SafeTransferLib for ERC20;
//==================== Adaptor Data Specification ====================
// adaptorData = abi.encode(address debtToken)
// Where:
// `debtToken` is the debtToken address position this adaptor is working with
//================= Configuration Data Specification =================
// NOT USED
//====================================================================
/**
@notice Attempted borrow would lower Cellar health factor too low.
*/
error AaveDebtTokenAdaptor__HealthFactorTooLow();
//============================================ Global Functions ===========================================
/**
* @dev Identifier unique to this adaptor for a shared registry.
* Normally the identifier would just be the address of this contract, but this
* Identifier is needed during Cellar Delegate Call Operations, so getting the address
* of the adaptor is more difficult.
*/
function identifier() public pure override returns (bytes32) {
return keccak256(abi.encode("Aave debtToken Adaptor V 0.0"));
}
/**
* @notice The Aave V2 Pool contract on Ethereum Mainnet.
*/
function pool() internal pure returns (IPool) {
return IPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9);
}
/**
* @notice Minimum Health Factor enforced after every borrow.
* @notice Overwrites strategist set minimums if they are lower.
*/
function HFMIN() internal pure returns (uint256) {
return 1.2e18;
}
//============================================ Implement Base Functions ===========================================
/**
* @notice User deposits are NOT allowed into this position.
*/
function deposit(
uint256,
bytes memory,
bytes memory
) public pure override {
revert BaseAdaptor__UserDepositsNotAllowed();
}
/**
* @notice User withdraws are NOT allowed from this position.
*/
function withdraw(
uint256,
address,
bytes memory,
bytes memory
) public pure override {
revert BaseAdaptor__UserWithdrawsNotAllowed();
}
/**
* @notice This position is a debt position, and user withdraws are not allowed so
* this position must return 0 for withdrawableFrom.
*/
function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) {
return 0;
}
/**
* @notice Returns the cellars balance of the positions debtToken.
*/
function balanceOf(bytes memory adaptorData) public view override returns (uint256) {
address token = abi.decode(adaptorData, (address));
return ERC20(token).balanceOf(msg.sender);
}
/**
* @notice Returns the positions debtToken underlying asset.
*/
function assetOf(bytes memory adaptorData) public view override returns (ERC20) {
IAaveToken token = IAaveToken(abi.decode(adaptorData, (address)));
return ERC20(token.UNDERLYING_ASSET_ADDRESS());
}
/**
* @notice This adaptor reports values in terms of debt.
*/
function isDebt() public pure override returns (bool) {
return true;
}
//============================================ Strategist Functions ===========================================
/**
* @notice Strategist attempted to open an untracked Aave loan.
* @param untrackedDebtPosition the address of the untracked loan
*/
error AaveDebtTokenAdaptor__DebtPositionsMustBeTracked(address untrackedDebtPosition);
/**
* @notice Allows strategists to borrow assets from Aave.
* @notice `debtTokenToBorrow` must be the debtToken, NOT the underlying ERC20.
* @param debtTokenToBorrow the debtToken to borrow on Aave
* @param amountToBorrow the amount of `debtTokenToBorrow` to borrow on Aave.
*/
function borrowFromAave(ERC20 debtTokenToBorrow, uint256 amountToBorrow) public {
// Check that debt position is properly set up to be tracked in the Cellar.
bytes32 positionHash = keccak256(abi.encode(identifier(), true, abi.encode(address(debtTokenToBorrow))));
uint32 positionId = Cellar(address(this)).registry().getPositionHashToPositionId(positionHash);
if (!Cellar(address(this)).isPositionUsed(positionId))
revert AaveDebtTokenAdaptor__DebtPositionsMustBeTracked(address(debtTokenToBorrow));
// Open up new variable debt position on Aave.
pool().borrow(
IAaveToken(address(debtTokenToBorrow)).UNDERLYING_ASSET_ADDRESS(),
amountToBorrow,
2,
0,
address(this)
); // 2 is the interest rate mode, either 1 for stable or 2 for variable
// Check that health factor is above adaptor minimum.
(, , , , , uint256 healthFactor) = pool().getUserAccountData(address(this));
if (healthFactor < HFMIN()) revert AaveDebtTokenAdaptor__HealthFactorTooLow();
}
/**
* @notice Allows strategists to repay loan debt on Aave.
* @dev Uses `_maxAvailable` helper function, see BaseAdaptor.sol
* @param tokenToRepay the underlying ERC20 token you want to repay, NOT the debtToken.
* @param amountToRepay the amount of `tokenToRepay` to repay with.
*/
function repayAaveDebt(ERC20 tokenToRepay, uint256 amountToRepay) public {
amountToRepay = _maxAvailable(tokenToRepay, amountToRepay);
tokenToRepay.safeApprove(address(pool()), amountToRepay);
pool().repay(address(tokenToRepay), amountToRepay, 2, address(this)); // 2 is the interest rate mode, either 1 for stable or 2 for variable
}
/**
* @notice Allows strategists to swap assets and repay loans in one call.
* @dev see `repayAaveDebt`, and BaseAdaptor.sol `swap`
*/
function swapAndRepay(
ERC20 tokenIn,
ERC20 tokenToRepay,
uint256 amountIn,
SwapRouter.Exchange exchange,
bytes memory params
) public {
uint256 amountToRepay = swap(tokenIn, tokenToRepay, amountIn, exchange, params);
repayAaveDebt(tokenToRepay, amountToRepay);
}
/**
* @notice allows strategist to have Cellars take out flash loans.
* @param loanToken address array of tokens to take out loans
* @param loanAmount uint256 array of loan amounts for each `loanToken`
* @dev `modes` is always a zero array meaning that this flash loan can NOT take on new debt positions, it must be paid in full.
*/
function flashLoan(
address[] memory loanToken,
uint256[] memory loanAmount,
bytes memory params
) public {
require(loanToken.length == loanAmount.length, "Input length mismatch.");
uint256[] memory modes = new uint256[](loanToken.length);
pool().flashLoan(address(this), loanToken, loanAmount, modes, address(this), params, 0);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorInterface {
function latestAnswer() external view returns (int256);
function latestTimestamp() external view returns (uint256);
function latestRound() external view returns (uint256);
function getAnswer(uint256 roundId) external view returns (int256);
function getTimestamp(uint256 roundId) external view returns (uint256);
event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);
event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./AggregatorInterface.sol";
import "./AggregatorV3Interface.sol";
interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AutomationCompatibleInterface {
/**
* @notice method that is simulated by the keepers to see if any work actually
* needs to be performed. This method does does not actually need to be
* executable, and since it is only ever simulated it can consume lots of gas.
* @dev To ensure that it is never called, you may want to add the
* cannotExecute modifier from KeeperBase to your implementation of this
* method.
* @param checkData specified in the upkeep registration so it is always the
* same for a registered upkeep. This can easily be broken down into specific
* arguments using `abi.decode`, so multiple upkeeps can be registered on the
* same contract and easily differentiated by the contract.
* @return upkeepNeeded boolean to indicate whether the keeper should call
* performUpkeep or not.
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try
* `abi.encode`.
*/
function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData);
/**
* @notice method that is actually executed by the keepers, via the registry.
* The data returned by the checkUpkeep simulation will be passed into
* this method to actually be executed.
* @dev The input to this method should not be trusted, and the caller of the
* method should not even be restricted to any single registry. Anyone should
* be able call it, and the input should be validated, there is no guarantee
* that the data passed in is the performData returned from checkUpkeep. This
* could happen due to malicious keepers, racing keepers, or simply a state
* change while the performUpkeep transaction is waiting for confirmation.
* Always validate the data passed in.
* @param performData is the data which was passed back from the checkData
* simulation. If it is encoded, it can easily be decoded into other types by
* calling `abi.decode`. This data should not be trusted, and should be
* validated against the contract's current state.
*/
function performUpkeep(bytes calldata performData) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol)
pragma solidity ^0.8.0;
import "../IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
*/
contract ERC721Holder is IERC721Receiver {
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toUint248(uint256 value) internal pure returns (uint248) {
require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toUint240(uint256 value) internal pure returns (uint240) {
require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toUint232(uint256 value) internal pure returns (uint232) {
require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
return uint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.2._
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toUint216(uint256 value) internal pure returns (uint216) {
require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toUint208(uint256 value) internal pure returns (uint208) {
require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toUint200(uint256 value) internal pure returns (uint200) {
require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toUint192(uint256 value) internal pure returns (uint192) {
require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toUint184(uint256 value) internal pure returns (uint184) {
require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toUint176(uint256 value) internal pure returns (uint176) {
require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toUint168(uint256 value) internal pure returns (uint168) {
require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toUint160(uint256 value) internal pure returns (uint160) {
require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toUint152(uint256 value) internal pure returns (uint152) {
require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toUint144(uint256 value) internal pure returns (uint144) {
require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toUint136(uint256 value) internal pure returns (uint136) {
require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
return uint136(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v2.5._
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toUint120(uint256 value) internal pure returns (uint120) {
require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toUint112(uint256 value) internal pure returns (uint112) {
require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toUint104(uint256 value) internal pure returns (uint104) {
require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
return uint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.2._
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toUint88(uint256 value) internal pure returns (uint88) {
require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toUint80(uint256 value) internal pure returns (uint80) {
require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toUint72(uint256 value) internal pure returns (uint72) {
require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
return uint72(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v2.5._
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toUint56(uint256 value) internal pure returns (uint56) {
require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toUint48(uint256 value) internal pure returns (uint48) {
require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toUint40(uint256 value) internal pure returns (uint40) {
require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
return uint40(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v2.5._
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toUint24(uint256 value) internal pure returns (uint24) {
require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
return uint24(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v2.5._
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*
* _Available since v2.5._
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*
* _Available since v3.0._
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.7._
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.7._
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*
* _Available since v3.0._
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed user, address indexed newOwner);
/*//////////////////////////////////////////////////////////////
OWNERSHIP STORAGE
//////////////////////////////////////////////////////////////*/
address public owner;
modifier onlyOwner() virtual {
require(msg.sender == owner, "UNAUTHORIZED");
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function transferOwnership(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Cellar } from "src/base/Cellar.sol";
import { ERC20 } from "src/base/ERC20.sol";
import { BaseAdaptor } from "src/modules/adaptors/BaseAdaptor.sol";
import { PriceRouter } from "src/modules/price-router/PriceRouter.sol";
contract Registry is Ownable {
// ============================================= ADDRESS CONFIG =============================================
/**
* @notice Emitted when the address of a contract is changed.
* @param id value representing the unique ID tied to the changed contract
* @param oldAddress address of the contract before the change
* @param newAddress address of the contract after the contract
*/
event AddressChanged(uint256 indexed id, address oldAddress, address newAddress);
/**
* @notice Attempted to set the address of a contract that is not registered.
* @param id id of the contract that is not registered
*/
error Registry__ContractNotRegistered(uint256 id);
/**
* @notice Emitted when depositor privilege changes.
* @param depositor depositor address
* @param state the new state of the depositor privilege
*/
event DepositorOnBehalfChanged(address depositor, bool state);
/**
* @notice The unique ID that the next registered contract will have.
*/
uint256 public nextId;
/**
* @notice Get the address associated with an id.
*/
mapping(uint256 => address) public getAddress;
/**
* @notice In order for an address to make deposits on behalf of users they must be approved.
*/
mapping(address => bool) public approvedForDepositOnBehalf;
/**
* @notice toggles a depositors ability to deposit into cellars on behalf of users.
*/
function setApprovedForDepositOnBehalf(address depositor, bool state) external onlyOwner {
approvedForDepositOnBehalf[depositor] = state;
emit DepositorOnBehalfChanged(depositor, state);
}
/**
* @notice Set the address of the contract at a given id.
*/
function setAddress(uint256 id, address newAddress) external onlyOwner {
if (id >= nextId) revert Registry__ContractNotRegistered(id);
emit AddressChanged(id, getAddress[id], newAddress);
getAddress[id] = newAddress;
}
// ============================================= INITIALIZATION =============================================
/**
* @param gravityBridge address of GravityBridge contract
* @param swapRouter address of SwapRouter contract
* @param priceRouter address of PriceRouter contract
*/
constructor(
address gravityBridge,
address swapRouter,
address priceRouter
) Ownable() {
_register(gravityBridge);
_register(swapRouter);
_register(priceRouter);
}
// ============================================ REGISTER CONFIG ============================================
/**
* @notice Emitted when a new contract is registered.
* @param id value representing the unique ID tied to the new contract
* @param newContract address of the new contract
*/
event Registered(uint256 indexed id, address indexed newContract);
/**
* @notice Register the address of a new contract.
* @param newContract address of the new contract to register
*/
function register(address newContract) external onlyOwner {
_register(newContract);
}
function _register(address newContract) internal {
getAddress[nextId] = newContract;
emit Registered(nextId, newContract);
nextId++;
}
// ============================================ FEE DISTRIBUTOR LOGIC ============================================
/**
* @notice Emitted when fees distributor is changed.
* @param oldFeesDistributor address of fee distributor was changed from
* @param newFeesDistributor address of fee distributor was changed to
*/
event FeesDistributorChanged(bytes32 oldFeesDistributor, bytes32 newFeesDistributor);
/**
* @notice Attempted to use an invalid cosmos address.
*/
error Registry__InvalidCosmosAddress();
bytes32 public feesDistributor = hex"000000000000000000000000b813554b423266bbd4c16c32fa383394868c1f55";
/**
* @notice Set the address of the fee distributor on the Sommelier chain.
* @dev IMPORTANT: Ensure that the address is formatted in the specific way that the Gravity contract
* expects it to be.
* @param newFeesDistributor formatted address of the new fee distributor module
*/
function setFeesDistributor(bytes32 newFeesDistributor) external onlyOwner {
if (uint256(newFeesDistributor) > type(uint160).max) revert Registry__InvalidCosmosAddress();
emit FeesDistributorChanged(feesDistributor, newFeesDistributor);
feesDistributor = newFeesDistributor;
}
// ============================================ POSITION LOGIC ============================================
/**
* @notice stores data related to Cellar positions.
* @param adaptors address of the adaptor to use for this position
* @param isDebt bool indicating whether this position takes on debt or not
* @param adaptorData arbitrary data needed to correclty set up a position
* @param configurationData arbitrary data settable by strategist to change cellar <-> adaptor interaction
*/
struct PositionData {
address adaptor;
bool isDebt;
bytes adaptorData;
bytes configurationData;
}
/**
* @notice stores data to help cellars manage their risk.
* @param assetRisk number 0 -> type(uint128).max indicating how risky a cellars assets can be
* 0: Safest
* 1: Riskiest
* @param protocolRisk number 0 -> type(uint128).max indicating how risky a cellars position protocol can be
* 0: Safest
* 1: Riskiest
*/
struct RiskData {
uint128 assetRisk;
uint128 protocolRisk;
}
/**
* @notice Emitted when a new position is added to the registry.
* @param id the positions id
* @param adaptor address of the adaptor this position uses
* @param isDebt bool indicating whether this position takes on debt or not
* @param adaptorData arbitrary bytes used to configure this position
*/
event PositionAdded(uint32 id, address adaptor, bool isDebt, bytes adaptorData);
/**
* @notice Attempted to trust a position not being used.
* @param position address of the invalid position
*/
error Registry__PositionPricingNotSetUp(address position);
/**
* @notice Attempted to add a position with bad input values.
*/
error Registry__InvalidPositionInput();
/**
* @notice Attempted to add a position with a risky asset.
*/
error Registry__AssetTooRisky();
/**
* @notice Attempted to add a position with a risky protocol.
*/
error Registry__ProtocolTooRisky();
/**
* @notice Attempted to add a position that does not exist.
*/
error Registry__PositionDoesNotExist();
/**
* @notice Addresses of the positions currently used by the cellar.
*/
uint256 public constant PRICE_ROUTER_REGISTRY_SLOT = 2;
/**
* @notice Maps a position Id to its risk data.
*/
mapping(uint32 => RiskData) public getRiskData;
/**
* @notice Maps an adaptor to its risk data.
*/
mapping(address => RiskData) public getAdaptorRiskData;
/**
* @notice Stores the number of positions that have been added to the registry.
* Starts at 1.
*/
uint32 public positionCount;
/**
* @notice Maps a position hash to a position Id.
* @dev can be used by adaptors to verify that a certain position is open during Cellar `callOnAdaptor` calls.
*/
mapping(bytes32 => uint32) public getPositionHashToPositionId;
/**
* @notice Maps a position id to its position data.
* @dev used by Cellars when adding new positions.
*/
mapping(uint32 => PositionData) public getPositionIdToPositionData;
/**
* @notice Trust a position to be used by the cellar.
* @param adaptor the adaptor address this position uses
* @param adaptorData arbitrary bytes used to configure this position
* @param assetRisk the risk rating of this positions asset
* @param protocolRisk the risk rating of this positions underlying protocol
* @return positionId the position id of the newly added position
*/
function trustPosition(
address adaptor,
bytes memory adaptorData,
uint128 assetRisk,
uint128 protocolRisk
) external onlyOwner returns (uint32 positionId) {
bytes32 identifier = BaseAdaptor(adaptor).identifier();
bool isDebt = BaseAdaptor(adaptor).isDebt();
bytes32 positionHash = keccak256(abi.encode(identifier, isDebt, adaptorData));
positionId = positionCount + 1; //Add one so that we do not use Id 0.
// Check that...
// `adaptor` is a non zero address
// position has not been already set up
if (adaptor == address(0) || getPositionHashToPositionId[positionHash] != 0)
revert Registry__InvalidPositionInput();
if (!isAdaptorTrusted[adaptor]) revert Registry__AdaptorNotTrusted();
// Set position data.
getPositionIdToPositionData[positionId] = PositionData({
adaptor: adaptor,
isDebt: isDebt,
adaptorData: adaptorData,
configurationData: abi.encode(0)
});
getRiskData[positionId] = RiskData({ assetRisk: assetRisk, protocolRisk: protocolRisk });
getPositionHashToPositionId[positionHash] = positionId;
// Check that assets position uses are supported for pricing operations.
ERC20[] memory assets = BaseAdaptor(adaptor).assetsUsed(adaptorData);
PriceRouter priceRouter = PriceRouter(getAddress[PRICE_ROUTER_REGISTRY_SLOT]);
for (uint256 i; i < assets.length; i++) {
if (!priceRouter.isSupported(assets[i])) revert Registry__PositionPricingNotSetUp(address(assets[i]));
}
positionCount = positionId;
emit PositionAdded(positionId, adaptor, isDebt, adaptorData);
}
/**
* @notice Called by Cellars to add a new position to themselves.
* @param positionId the id of the position the cellar wants to add
* @param assetRiskTolerance the cellars risk tolerance for assets
* @param protocolRiskTolerance the cellars risk tolerance for protocols
* @return adaptor the address of the adaptor, isDebt bool indicating whether position is
* debt or not, and adaptorData needed to interact with position
*/
function cellarAddPosition(
uint32 positionId,
uint128 assetRiskTolerance,
uint128 protocolRiskTolerance
)
external
view
returns (
address adaptor,
bool isDebt,
bytes memory adaptorData
)
{
if (positionId > positionCount || positionId == 0) revert Registry__PositionDoesNotExist();
RiskData memory data = getRiskData[positionId];
if (assetRiskTolerance < data.assetRisk) revert Registry__AssetTooRisky();
if (protocolRiskTolerance < data.protocolRisk) revert Registry__ProtocolTooRisky();
PositionData memory positionData = getPositionIdToPositionData[positionId];
return (positionData.adaptor, positionData.isDebt, positionData.adaptorData);
}
// ============================================ ADAPTOR LOGIC ============================================
/**
* @notice Attempted to trust an adaptor with non unique identifier.
*/
error Registry__IdentifierNotUnique();
/**
* @notice Attempted to use an untrusted adaptor.
*/
error Registry__AdaptorNotTrusted();
/**
* @notice Maps an adaptor address to bool indicating whether it has been set up in the registry.
*/
mapping(address => bool) public isAdaptorTrusted;
/**
* @notice Maps an adaptors identier to bool, to track if the indentifier is unique wrt the registry.
*/
mapping(bytes32 => bool) public isIdentifierUsed;
/**
* @notice Trust an adaptor to be used by cellars
* @param adaptor address of the adaptor to trust
* @param assetRisk the asset risk level associated with this adaptor
* @param protocolRisk the protocol risk level associated with this adaptor
*/
function trustAdaptor(
address adaptor,
uint128 assetRisk,
uint128 protocolRisk
) external onlyOwner {
bytes32 identifier = BaseAdaptor(adaptor).identifier();
if (isIdentifierUsed[identifier]) revert Registry__IdentifierNotUnique();
isAdaptorTrusted[adaptor] = true;
isIdentifierUsed[identifier] = true;
getAdaptorRiskData[adaptor] = RiskData({ assetRisk: assetRisk, protocolRisk: protocolRisk });
}
/**
* @notice Called by Cellars to allow them to use new adaptors.
* @param adaptor address of the adaptor to use
* @param assetRiskTolerance asset risk tolerance of the caller
* @param protocolRiskTolerance protocol risk tolerance of the cellar
*/
function cellarSetupAdaptor(
address adaptor,
uint128 assetRiskTolerance,
uint128 protocolRiskTolerance
) external view {
RiskData memory data = getAdaptorRiskData[adaptor];
if (assetRiskTolerance < data.assetRisk) revert Registry__AssetTooRisky();
if (protocolRiskTolerance < data.protocolRisk) revert Registry__ProtocolTooRisky();
if (!isAdaptorTrusted[adaptor]) revert Registry__AdaptorNotTrusted();
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { ERC4626, SafeTransferLib, Math, ERC20 } from "./ERC4626.sol";
import { Registry } from "src/Registry.sol";
import { PriceRouter } from "src/modules/price-router/PriceRouter.sol";
import { IGravity } from "src/interfaces/external/IGravity.sol";
import { Uint32Array } from "src/utils/Uint32Array.sol";
import { BaseAdaptor } from "src/modules/adaptors/BaseAdaptor.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import { Owned } from "@solmate/auth/Owned.sol";
/**
* @title Sommelier Cellar
* @notice A composable ERC4626 that can use arbitrary DeFi assets/positions using adaptors.
* @author crispymangoes
*/
contract Cellar is ERC4626, Owned, ERC721Holder {
using Uint32Array for uint32[];
using SafeTransferLib for ERC20;
using Math for uint256;
using Address for address;
// ========================================= REENTRANCY GUARD =========================================
/**
* @notice `locked` is public, so that the state can be checked even during view function calls.
*/
uint256 public locked = 1;
modifier nonReentrant() {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
// ========================================= POSITIONS CONFIG =========================================
/**
* @notice Emitted when a position is added.
* @param position id of position that was added
* @param index index that position was added at
*/
event PositionAdded(uint32 position, uint256 index);
/**
* @notice Emitted when a position is removed.
* @param position id of position that was removed
* @param index index that position was removed from
*/
event PositionRemoved(uint32 position, uint256 index);
/**
* @notice Emitted when the positions at two indexes are swapped.
* @param newPosition1 id of position (previously at index2) that replaced index1.
* @param newPosition2 id of position (previously at index1) that replaced index2.
* @param index1 index of first position involved in the swap
* @param index2 index of second position involved in the swap.
*/
event PositionSwapped(uint32 newPosition1, uint32 newPosition2, uint256 index1, uint256 index2);
/**
* @notice Attempted to add a position that is already being used.
* @param position id of the position
*/
error Cellar__PositionAlreadyUsed(uint32 position);
/**
* @notice Attempted to make an unused position the holding position.
* @param position id of the position
*/
error Cellar__PositionNotUsed(uint32 position);
/**
* @notice Attempted an action on a position that is required to be empty before the action can be performed.
* @param position address of the non-empty position
* @param sharesRemaining amount of shares remaining in the position
*/
error Cellar__PositionNotEmpty(uint32 position, uint256 sharesRemaining);
/**
* @notice Attempted an operation with an asset that was different then the one expected.
* @param asset address of the asset
* @param expectedAsset address of the expected asset
*/
error Cellar__AssetMismatch(address asset, address expectedAsset);
/**
* @notice Attempted to add a position when the position array is full.
* @param maxPositions maximum number of positions that can be used
*/
error Cellar__PositionArrayFull(uint256 maxPositions);
/**
* @notice Attempted to add a position, with mismatched debt.
* @param position the posiiton id that was mismatched
*/
error Cellar__DebtMismatch(uint32 position);
/**
* @notice Attempted to remove the Cellars holding position.
*/
error Cellar__RemovingHoldingPosition();
/**
* @notice Attempted to add an invalid holding position.
* @param positionId the id of the invalid position.
*/
error Cellar__InvalidHoldingPosition(uint32 positionId);
/**
* @notice Array of uint32s made up of cellars credit positions Ids.
*/
uint32[] public creditPositions;
/**
* @notice Array of uint32s made up of cellars debt positions Ids.
*/
uint32[] public debtPositions;
/**
* @notice Tell whether a position is currently used.
*/
mapping(uint256 => bool) public isPositionUsed;
/**
* @notice Get position data given position id.
*/
mapping(uint32 => Registry.PositionData) public getPositionData;
/**
* @notice Get the ids of the credit positions currently used by the cellar.
*/
function getCreditPositions() external view returns (uint32[] memory) {
return creditPositions;
}
/**
* @notice Get the ids of the debt positions currently used by the cellar.
*/
function getDebtPositions() external view returns (uint32[] memory) {
return debtPositions;
}
/**
* @notice Maximum amount of positions a cellar can have in it's credit/debt arrays.
*/
uint256 public constant MAX_POSITIONS = 16;
/**
* @notice Stores the index of the holding position in the creditPositions array.
*/
uint32 public holdingPosition;
/**
* @notice Allows owner to change the holding position.
*/
function setHoldingPosition(uint32 positionId) external onlyOwner {
_setHoldingPosition(positionId);
}
function _setHoldingPosition(uint32 positionId) internal {
if (!isPositionUsed[positionId]) revert Cellar__PositionNotUsed(positionId);
if (_assetOf(positionId) != asset) revert Cellar__AssetMismatch(address(asset), address(_assetOf(positionId)));
if (getPositionData[positionId].isDebt) revert Cellar__InvalidHoldingPosition(positionId);
holdingPosition = positionId;
}
/**
* @notice Insert a trusted position to the list of positions used by the cellar at a given index.
* @param index index at which to insert the position
* @param positionId id of position to add
* @param configurationData data used to configure how the position behaves
*/
function addPosition(
uint32 index,
uint32 positionId,
bytes memory configurationData,
bool inDebtArray
) external onlyOwner whenNotShutdown {
_addPosition(index, positionId, configurationData, inDebtArray);
}
/**
* @notice Internal function ise used by `addPosition` and initialize function.
*/
function _addPosition(
uint32 index,
uint32 positionId,
bytes memory configurationData,
bool inDebtArray
) internal {
// Check if position is already being used.
if (isPositionUsed[positionId]) revert Cellar__PositionAlreadyUsed(positionId);
// Grab position data from registry.
(address adaptor, bool isDebt, bytes memory adaptorData) = registry.cellarAddPosition(
positionId,
assetRiskTolerance,
protocolRiskTolerance
);
if (isDebt != inDebtArray) revert Cellar__DebtMismatch(positionId);
// Copy position data from registry to here.
getPositionData[positionId] = Registry.PositionData({
adaptor: adaptor,
isDebt: isDebt,
adaptorData: adaptorData,
configurationData: configurationData
});
if (isDebt) {
if (debtPositions.length >= MAX_POSITIONS) revert Cellar__PositionArrayFull(MAX_POSITIONS);
// Add new position at a specified index.
debtPositions.add(index, positionId);
} else {
if (creditPositions.length >= MAX_POSITIONS) revert Cellar__PositionArrayFull(MAX_POSITIONS);
// Add new position at a specified index.
creditPositions.add(index, positionId);
}
isPositionUsed[positionId] = true;
emit PositionAdded(positionId, index);
}
/**
* @notice Remove the position at a given index from the list of positions used by the cellar.
* @param index index at which to remove the position
*/
function removePosition(uint32 index, bool inDebtArray) external onlyOwner {
// Get position being removed.
uint32 positionId = inDebtArray ? debtPositions[index] : creditPositions[index];
if (positionId == holdingPosition) revert Cellar__RemovingHoldingPosition();
// Only remove position if it is empty, and if it is not the holding position.
uint256 positionBalance = _balanceOf(positionId);
if (positionBalance > 0) revert Cellar__PositionNotEmpty(positionId, positionBalance);
if (inDebtArray) {
// Remove position at the given index.
debtPositions.remove(index);
} else {
creditPositions.remove(index);
}
isPositionUsed[positionId] = false;
delete getPositionData[positionId];
emit PositionRemoved(positionId, index);
}
/**
* @notice Swap the positions at two given indexes.
* @param index1 index of first position to swap
* @param index2 index of second position to swap
* @param inDebtArray bool indicating to switch positions in the debt array, or the credit array.
*/
function swapPositions(
uint32 index1,
uint32 index2,
bool inDebtArray
) external onlyOwner {
// Get the new positions that will be at each index.
uint32 newPosition1;
uint32 newPosition2;
if (inDebtArray) {
newPosition1 = debtPositions[index2];
newPosition2 = debtPositions[index1];
// Swap positions.
(debtPositions[index1], debtPositions[index2]) = (newPosition1, newPosition2);
} else {
newPosition1 = creditPositions[index2];
newPosition2 = creditPositions[index1];
// Swap positions.
(creditPositions[index1], creditPositions[index2]) = (newPosition1, newPosition2);
}
emit PositionSwapped(newPosition1, newPosition2, index1, index2);
}
// =============================================== FEES CONFIG ===============================================
/**
* @notice Emitted when platform fees is changed.
* @param oldPlatformFee value platform fee was changed from
* @param newPlatformFee value platform fee was changed to
*/
event PlatformFeeChanged(uint64 oldPlatformFee, uint64 newPlatformFee);
/**
* @notice Emitted when strategist platform fee cut is changed.
* @param oldPlatformCut value strategist platform fee cut was changed from
* @param newPlatformCut value strategist platform fee cut was changed to
*/
event StrategistPlatformCutChanged(uint64 oldPlatformCut, uint64 newPlatformCut);
/**
* @notice Emitted when strategists payout address is changed.
* @param oldPayoutAddress value strategists payout address was changed from
* @param newPayoutAddress value strategists payout address was changed to
*/
event StrategistPayoutAddressChanged(address oldPayoutAddress, address newPayoutAddress);
/**
* @notice Attempted to change strategist fee cut with invalid value.
*/
error Cellar__InvalidFeeCut();
/**
* @notice Attempted to change platform fee with invalid value.
*/
error Cellar__InvalidFee();
/**
* @notice Data related to fees.
* @param strategistPlatformCut Determines how much platform fees go to strategist.
* This should be a value out of 1e18 (ie. 1e18 represents 100%, 0 represents 0%).
* @param platformFee The percentage of total assets accrued as platform fees over a year.
This should be a value out of 1e18 (ie. 1e18 represents 100%, 0 represents 0%).
* @param strategistPayoutAddress Address to send the strategists fee shares.
*/
struct FeeData {
uint64 strategistPlatformCut;
uint64 platformFee;
uint64 lastAccrual;
address strategistPayoutAddress;
}
/**
* @notice Stores all fee data for cellar.
*/
FeeData public feeData =
FeeData({
strategistPlatformCut: 0.75e18,
platformFee: 0.01e18,
lastAccrual: 0,
strategistPayoutAddress: address(0)
});
/**
* @notice Sets the max possible performance fee for this cellar.
*/
uint64 public constant MAX_PLATFORM_FEE = 0.2e18;
/**
* @notice Sets the max possible fee cut for this cellar.
*/
uint64 public constant MAX_FEE_CUT = 1e18;
/**
* @notice Set the percentage of platform fees accrued over a year.
* @param newPlatformFee value out of 1e18 that represents new platform fee percentage
*/
function setPlatformFee(uint64 newPlatformFee) external onlyOwner {
if (newPlatformFee > MAX_PLATFORM_FEE) revert Cellar__InvalidFee();
emit PlatformFeeChanged(feeData.platformFee, newPlatformFee);
feeData.platformFee = newPlatformFee;
}
/**
* @notice Sets the Strategists cut of platform fees
* @param cut the platform cut for the strategist
*/
function setStrategistPlatformCut(uint64 cut) external onlyOwner {
if (cut > MAX_FEE_CUT) revert Cellar__InvalidFeeCut();
emit StrategistPlatformCutChanged(feeData.strategistPlatformCut, cut);
feeData.strategistPlatformCut = cut;
}
/**
* @notice Sets the Strategists payout address
* @param payout the new strategist payout address
*/
function setStrategistPayoutAddress(address payout) external onlyOwner {
emit StrategistPayoutAddressChanged(feeData.strategistPayoutAddress, payout);
feeData.strategistPayoutAddress = payout;
}
// =========================================== EMERGENCY LOGIC ===========================================
/**
* @notice Emitted when cellar emergency state is changed.
* @param isShutdown whether the cellar is shutdown
*/
event ShutdownChanged(bool isShutdown);
/**
* @notice Attempted action was prevented due to contract being shutdown.
*/
error Cellar__ContractShutdown();
/**
* @notice Attempted action was prevented due to contract not being shutdown.
*/
error Cellar__ContractNotShutdown();
/**
* @notice Whether or not the contract is shutdown in case of an emergency.
*/
bool public isShutdown;
/**
* @notice Prevent a function from being called during a shutdown.
*/
modifier whenNotShutdown() {
if (isShutdown) revert Cellar__ContractShutdown();
_;
}
/**
* @notice Shutdown the cellar. Used in an emergency or if the cellar has been deprecated.
* @dev In the case where
*/
function initiateShutdown() external whenNotShutdown onlyOwner {
isShutdown = true;
emit ShutdownChanged(true);
}
/**
* @notice Restart the cellar.
*/
function liftShutdown() external onlyOwner {
if (!isShutdown) revert Cellar__ContractNotShutdown();
isShutdown = false;
emit ShutdownChanged(false);
}
// =========================================== CONSTRUCTOR ===========================================
/**
* @notice Id to get the gravity bridge from the registry.
*/
uint256 public constant GRAVITY_BRIDGE_REGISTRY_SLOT = 0;
/**
* @notice Id to get the price router from the registry.
*/
uint256 public constant PRICE_ROUTER_REGISTRY_SLOT = 2;
/**
* @notice Address of the platform's registry contract. Used to get the latest address of modules.
*/
Registry public registry;
/**
* @notice Determines this cellars risk tolerance in regards to assets it is exposed to.
* @dev 0: safest
* type(uint128).max: no restrictions
*/
uint128 public assetRiskTolerance;
/**
* @notice Determines this cellars risk tolerance in regards to protocols it uses.
* @dev 0: safest
* type(uint128).max: no restrictions
*/
uint128 public protocolRiskTolerance;
/**
* @dev Owner should be set to the Gravity Bridge, which relays instructions from the Steward
* module to the cellars.
* https://github.com/PeggyJV/steward
* https://github.com/cosmos/gravity-bridge/blob/main/solidity/contracts/Gravity.sol
* @param _registry address of the platform's registry contract
* @param _asset address of underlying token used for the for accounting, depositing, and withdrawing
* @param _name name of this cellar's share token
* @param _symbol symbol of this cellar's share token
* @param params abi encode values.
* - _creditPositions ids of the credit positions to initialize the cellar with
* - _debtPositions ids of the credit positions to initialize the cellar with
* - _creditConfigurationData configuration data for each position
* - _debtConfigurationData configuration data for each position
* - _holdingIndex the index in _creditPositions to use as the holding position.
* - _strategistPayout the address to send the strategists fee shares.
* - _assetRiskTolerance this cellars risk tolerance for assets it is exposed to
* - _protocolRiskTolerance this cellars risk tolerance for protocols it will use
*/
constructor(
Registry _registry,
ERC20 _asset,
string memory _name,
string memory _symbol,
bytes memory params
) ERC4626(_asset, _name, _symbol, 18) Owned(_registry.getAddress(GRAVITY_BRIDGE_REGISTRY_SLOT)) {
registry = _registry;
{
(
uint32[] memory _creditPositions,
uint32[] memory _debtPositions,
bytes[] memory _creditConfigurationData,
bytes[] memory _debtConfigurationData,
uint32 _holdingPosition
) = abi.decode(params, (uint32[], uint32[], bytes[], bytes[], uint8));
// Initialize positions.
for (uint32 i; i < _creditPositions.length; ++i) {
_addPosition(i, _creditPositions[i], _creditConfigurationData[i], false);
}
for (uint32 i; i < _debtPositions.length; ++i) {
_addPosition(i, _debtPositions[i], _debtConfigurationData[i], true);
}
// This check allows us to deploy an implementation contract.
/// @dev No cellars will be deployed with a zero length credit positions array.
if (_creditPositions.length > 0) _setHoldingPosition(_holdingPosition);
}
// Initialize last accrual timestamp to time that cellar was created, otherwise the first
// `accrue` will take platform fees from 1970 to the time it is called.
feeData.lastAccrual = uint64(block.timestamp);
(, , , , , address _strategistPayout, uint128 _assetRiskTolerance, uint128 _protocolRiskTolerance) = abi.decode(
params,
(uint32[], uint32[], bytes[], bytes[], uint8, address, uint128, uint128)
);
feeData.strategistPayoutAddress = _strategistPayout;
assetRiskTolerance = _assetRiskTolerance;
protocolRiskTolerance = _protocolRiskTolerance;
}
// =========================================== CORE LOGIC ===========================================
/**
* @notice Emitted when share locking period is changed.
* @param oldPeriod the old locking period
* @param newPeriod the new locking period
*/
event ShareLockingPeriodChanged(uint256 oldPeriod, uint256 newPeriod);
/**
* @notice Attempted an action with zero shares.
*/
error Cellar__ZeroShares();
/**
* @notice Attempted an action with zero assets.
*/
error Cellar__ZeroAssets();
/**
* @notice Withdraw did not withdraw all assets.
* @param assetsOwed the remaining assets owed that were not withdrawn.
*/
error Cellar__IncompleteWithdraw(uint256 assetsOwed);
/**
* @notice Attempted to withdraw an illiquid position.
* @param illiquidPosition the illiquid position.
*/
error Cellar__IlliquidWithdraw(address illiquidPosition);
/**
* @notice Attempted to set `shareLockPeriod` to an invalid number.
*/
error Cellar__InvalidShareLockPeriod();
/**
* @notice Attempted to burn shares when they are locked.
* @param timeSharesAreUnlocked time when caller can transfer/redeem shares
* @param currentBlock the current block number.
*/
error Cellar__SharesAreLocked(uint256 timeSharesAreUnlocked, uint256 currentBlock);
/**
* @notice Attempted deposit on behalf of a user without being approved.
*/
error Cellar__NotApprovedToDepositOnBehalf(address depositor);
/**
* @notice Shares must be locked for at least 5 minutes after minting.
*/
uint256 public constant MINIMUM_SHARE_LOCK_PERIOD = 5 * 60;
/**
* @notice Shares can be locked for at most 2 days after minting.
*/
uint256 public constant MAXIMUM_SHARE_LOCK_PERIOD = 2 days;
/**
* @notice After deposits users must wait `shareLockPeriod` time before being able to transfer or withdraw their shares.
*/
uint256 public shareLockPeriod = MAXIMUM_SHARE_LOCK_PERIOD;
/**
* @notice mapping that stores every users last time stamp they minted shares.
*/
mapping(address => uint256) public userShareLockStartTime;
/**
* @notice Allows share lock period to be updated.
* @param newLock the new lock period
*/
function setShareLockPeriod(uint256 newLock) external onlyOwner {
if (newLock < MINIMUM_SHARE_LOCK_PERIOD || newLock > MAXIMUM_SHARE_LOCK_PERIOD)
revert Cellar__InvalidShareLockPeriod();
uint256 oldLockingPeriod = shareLockPeriod;
shareLockPeriod = newLock;
emit ShareLockingPeriodChanged(oldLockingPeriod, newLock);
}
/**
* @notice helper function that checks enough time has passed to unlock shares.
* @param owner the address of the user to check
*/
function _checkIfSharesLocked(address owner) internal view {
uint256 lockTime = userShareLockStartTime[owner];
if (lockTime != 0) {
uint256 timeSharesAreUnlocked = lockTime + shareLockPeriod;
if (timeSharesAreUnlocked > block.timestamp)
revert Cellar__SharesAreLocked(timeSharesAreUnlocked, block.timestamp);
}
}
/**
* @notice Override `transfer` to add share lock check.
*/
function transfer(address to, uint256 amount) public override returns (bool) {
_checkIfSharesLocked(msg.sender);
return super.transfer(to, amount);
}
/**
* @notice Override `transferFrom` to add share lock check.
*/
function transferFrom(
address from,
address to,
uint256 amount
) public override returns (bool) {
_checkIfSharesLocked(from);
return super.transferFrom(from, to, amount);
}
/**
* @notice Attempted deposit more than the max deposit.
* @param assets the assets user attempted to deposit
* @param maxDeposit the max assets that can be deposited
*/
error Cellar__DepositRestricted(uint256 assets, uint256 maxDeposit);
/**
* @notice called at the beginning of deposit.
* @param assets amount of assets deposited by user.
* @param receiver address receiving the shares.
*/
function beforeDeposit(
uint256 assets,
uint256,
address receiver
) internal view override whenNotShutdown {
if (msg.sender != receiver) {
if (!registry.approvedForDepositOnBehalf(msg.sender))
revert Cellar__NotApprovedToDepositOnBehalf(msg.sender);
}
uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) revert Cellar__DepositRestricted(assets, maxAssets);
}
/**
* @notice called at the end of deposit.
* @param assets amount of assets deposited by user.
*/
function afterDeposit(
uint256 assets,
uint256,
address receiver
) internal override {
_depositTo(holdingPosition, assets);
userShareLockStartTime[receiver] = block.timestamp;
}
/**
* @notice called at the beginning of withdraw.
*/
function beforeWithdraw(
uint256,
uint256,
address,
address owner
) internal view override {
// Make sure users shares are not locked.
_checkIfSharesLocked(owner);
}
function _enter(
uint256 assets,
uint256 shares,
address receiver
) internal {
beforeDeposit(assets, shares, receiver);
// 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, receiver);
}
/**
* @notice Deposits assets into the cellar, and returns shares to receiver.
* @param assets amount of assets deposited by user.
* @param receiver address to receive the shares.
* @return shares amount of shares given for deposit.
*/
function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares) {
// Use `_accounting` instead of totalAssets bc re-entrancy is already checked in this function.
uint256 _totalAssets = _accounting(false);
// Check for rounding error since we round down in previewDeposit.
if ((shares = _convertToShares(assets, _totalAssets)) == 0) revert Cellar__ZeroShares();
_enter(assets, shares, receiver);
}
/**
* @notice Mints shares from the cellar, and returns shares to receiver.
* @param shares amount of shares requested by user.
* @param receiver address to receive the shares.
* @return assets amount of assets deposited into the cellar.
*/
function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256 assets) {
// Use `_accounting` instead of totalAssets bc re-entrancy is already checked in this function.
uint256 _totalAssets = _accounting(false);
// previewMint rounds up, but initial mint could return zero assets, so check for rounding error.
if ((assets = _previewMint(shares, _totalAssets)) == 0) revert Cellar__ZeroAssets();
_enter(assets, shares, receiver);
}
function _exit(
uint256 assets,
uint256 shares,
address receiver,
address owner
) internal {
beforeWithdraw(assets, shares, receiver, owner);
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;
}
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
_withdrawInOrder(assets, receiver);
/// @notice `afterWithdraw` is currently not used.
// afterWithdraw(assets, shares, receiver, owner);
}
/**
* @notice Withdraw assets from the cellar by redeeming shares.
* @dev Unlike conventional ERC4626 contracts, this may not always return one asset to the receiver.
* Since there are no swaps involved in this function, the receiver may receive multiple
* assets. The value of all the assets returned will be equal to the amount defined by
* `assets` denominated in the `asset` of the cellar (eg. if `asset` is USDC and `assets`
* is 1000, then the receiver will receive $1000 worth of assets in either one or many
* tokens).
* @param assets equivalent value of the assets withdrawn, denominated in the cellar's asset
* @param receiver address that will receive withdrawn assets
* @param owner address that owns the shares being redeemed
* @return shares amount of shares redeemed
*/
function withdraw(
uint256 assets,
address receiver,
address owner
) public override nonReentrant returns (uint256 shares) {
// Use `_accounting` instead of totalAssets bc re-entrancy is already checked in this function.
uint256 _totalAssets = _accounting(false);
// No need to check for rounding error, `previewWithdraw` rounds up.
shares = _previewWithdraw(assets, _totalAssets);
_exit(assets, shares, receiver, owner);
}
/**
* @notice Redeem shares to withdraw assets from the cellar.
* @dev Unlike conventional ERC4626 contracts, this may not always return one asset to the receiver.
* Since there are no swaps involved in this function, the receiver may receive multiple
* assets. The value of all the assets returned will be equal to the amount defined by
* `assets` denominated in the `asset` of the cellar (eg. if `asset` is USDC and `assets`
* is 1000, then the receiver will receive $1000 worth of assets in either one or many
* tokens).
* @param shares amount of shares to redeem
* @param receiver address that will receive withdrawn assets
* @param owner address that owns the shares being redeemed
* @return assets equivalent value of the assets withdrawn, denominated in the cellar's asset
*/
function redeem(
uint256 shares,
address receiver,
address owner
) public override nonReentrant returns (uint256 assets) {
// Use `_accounting` instead of totalAssets bc re-entrancy is already checked in this function.
uint256 _totalAssets = _accounting(false);
// Check for rounding error since we round down in previewRedeem.
if ((assets = _convertToAssets(shares, _totalAssets)) == 0) revert Cellar__ZeroAssets();
_exit(assets, shares, receiver, owner);
}
/**
* @notice Struct used in `_withdrawInOrder` in order to hold multiple pricing values in a single variable.
* @dev Prevents stack too deep errors.
*/
struct WithdrawPricing {
uint256 priceBaseUSD;
uint256 oneBase;
uint256 priceQuoteUSD;
uint256 oneQuote;
}
/**
* @notice Multipler used to insure calculations use very high precision.
*/
uint256 private constant PRECISION_MULTIPLIER = 1e18;
/**
* @dev Withdraw from positions in the order defined by `positions`.
* @param assets the amount of assets to withdraw from cellar
* @param receiver the address to sent withdrawn assets to
* @dev Only loop through credit array because debt can not be withdraw by users.
*/
function _withdrawInOrder(uint256 assets, address receiver) internal {
// Get the price router.
PriceRouter priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
// Save asset price in USD, and decimals to reduce external calls.
WithdrawPricing memory pricingInfo;
pricingInfo.priceQuoteUSD = priceRouter.getPriceInUSD(asset);
pricingInfo.oneQuote = 10**asset.decimals();
uint256 creditLength = creditPositions.length;
for (uint256 i; i < creditLength; ++i) {
uint32 position = creditPositions[i];
uint256 withdrawableBalance = _withdrawableFrom(position);
// Move on to next position if this one is empty.
if (withdrawableBalance == 0) continue;
ERC20 positionAsset = _assetOf(position);
pricingInfo.priceBaseUSD = priceRouter.getPriceInUSD(positionAsset);
pricingInfo.oneBase = 10**positionAsset.decimals();
uint256 totalWithdrawableBalanceInAssets;
{
uint256 withdrawableBalanceInUSD = (PRECISION_MULTIPLIER * withdrawableBalance).mulDivDown(
pricingInfo.priceBaseUSD,
pricingInfo.oneBase
);
totalWithdrawableBalanceInAssets = withdrawableBalanceInUSD.mulDivDown(
pricingInfo.oneQuote,
pricingInfo.priceQuoteUSD
);
totalWithdrawableBalanceInAssets = totalWithdrawableBalanceInAssets / PRECISION_MULTIPLIER;
}
// We want to pull as much as we can from this position, but no more than needed.
uint256 amount;
if (totalWithdrawableBalanceInAssets > assets) {
// Convert assets into position asset.
uint256 assetsInUSD = (PRECISION_MULTIPLIER * assets).mulDivDown(
pricingInfo.priceQuoteUSD,
pricingInfo.oneQuote
);
amount = assetsInUSD.mulDivDown(pricingInfo.oneBase, pricingInfo.priceBaseUSD);
amount = amount / PRECISION_MULTIPLIER;
assets = 0;
} else {
amount = withdrawableBalance;
assets = assets - totalWithdrawableBalanceInAssets;
}
// Withdraw from position.
_withdrawFrom(position, amount, receiver);
// Stop if no more assets to withdraw.
if (assets == 0) break;
}
// If withdraw did not remove all assets owed, revert.
if (assets > 0) revert Cellar__IncompleteWithdraw(assets);
}
// ========================================= ACCOUNTING LOGIC =========================================
/**
* @notice Internal accounting function that can report total assets, or total assets withdrawable.
* @param reportWithdrawable if true, then the withdrawable total assets is reported,
* if false, then the total assets is reported
*/
function _accounting(bool reportWithdrawable) internal view returns (uint256 assets) {
uint256 numOfCreditPositions = creditPositions.length;
ERC20[] memory creditAssets = new ERC20[](numOfCreditPositions);
uint256[] memory creditBalances = new uint256[](numOfCreditPositions);
PriceRouter priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
// If we just need the withdrawable, then query credit array value.
if (reportWithdrawable) {
for (uint256 i; i < numOfCreditPositions; ++i) {
uint32 position = creditPositions[i];
// If the withdrawable balance is zero there is no point to query the asset since a zero balance has zero value.
if ((creditBalances[i] = _withdrawableFrom(position)) == 0) continue;
creditAssets[i] = _assetOf(position);
}
assets = priceRouter.getValues(creditAssets, creditBalances, asset);
} else {
uint256 numOfDebtPositions = debtPositions.length;
ERC20[] memory debtAssets = new ERC20[](numOfDebtPositions);
uint256[] memory debtBalances = new uint256[](numOfDebtPositions);
for (uint256 i; i < numOfCreditPositions; ++i) {
uint32 position = creditPositions[i];
// If the balance is zero there is no point to query the asset since a zero balance has zero value.
if ((creditBalances[i] = _balanceOf(position)) == 0) continue;
creditAssets[i] = _assetOf(position);
}
for (uint256 i; i < numOfDebtPositions; ++i) {
uint32 position = debtPositions[i];
// If the balance is zero there is no point to query the asset since a zero balance has zero value.
if ((debtBalances[i] = _balanceOf(position)) == 0) continue;
debtAssets[i] = _assetOf(position);
}
assets = priceRouter.getValuesDelta(creditAssets, creditBalances, debtAssets, debtBalances, asset);
}
}
/**
* @notice The total amount of assets in the cellar.
* @dev EIP4626 states totalAssets needs to be inclusive of fees.
* Since performance fees mint shares, total assets remains unchanged,
* so this implementation is inclusive of fees even though it does not explicitly show it.
* @dev EIP4626 states totalAssets must not revert, but it is possible for `totalAssets` to revert
* so it does NOT conform to ERC4626 standards.
* @dev Run a re-entrancy check because totalAssets can be wrong if re-entering from deposit/withdraws.
*/
function totalAssets() public view override returns (uint256 assets) {
require(locked == 1, "REENTRANCY");
assets = _accounting(false);
}
/**
* @notice The total amount of withdrawable assets in the cellar.
* @dev Run a re-entrancy check because totalAssetsWithdrawable can be wrong if re-entering from deposit/withdraws.
*/
function totalAssetsWithdrawable() public view returns (uint256 assets) {
require(locked == 1, "REENTRANCY");
assets = _accounting(true);
}
/**
* @notice The amount of assets that the cellar would exchange for the amount of shares provided.
* @param shares amount of shares to convert
* @return assets the shares can be exchanged for
*/
function convertToAssets(uint256 shares) public view override returns (uint256 assets) {
assets = _convertToAssets(shares, totalAssets());
}
/**
* @notice The amount of shares that the cellar would exchange for the amount of assets provided.
* @param assets amount of assets to convert
* @return shares the assets can be exchanged for
*/
function convertToShares(uint256 assets) public view override returns (uint256 shares) {
shares = _convertToShares(assets, totalAssets());
}
/**
* @notice Simulate the effects of minting shares at the current block, given current on-chain conditions.
* @param shares amount of shares to mint
* @return assets that will be deposited
*/
function previewMint(uint256 shares) public view override returns (uint256 assets) {
uint256 _totalAssets = totalAssets();
assets = _previewMint(shares, _totalAssets);
}
/**
* @notice Simulate the effects of withdrawing assets at the current block, given current on-chain conditions.
* @param assets amount of assets to withdraw
* @return shares that will be redeemed
*/
function previewWithdraw(uint256 assets) public view override returns (uint256 shares) {
uint256 _totalAssets = totalAssets();
shares = _previewWithdraw(assets, _totalAssets);
}
/**
* @notice Simulate the effects of depositing assets at the current block, given current on-chain conditions.
* @param assets amount of assets to deposit
* @return shares that will be minted
*/
function previewDeposit(uint256 assets) public view override returns (uint256 shares) {
uint256 _totalAssets = totalAssets();
shares = _convertToShares(assets, _totalAssets);
}
/**
* @notice Simulate the effects of redeeming shares at the current block, given current on-chain conditions.
* @param shares amount of shares to redeem
* @return assets that will be returned
*/
function previewRedeem(uint256 shares) public view override returns (uint256 assets) {
uint256 _totalAssets = totalAssets();
assets = _convertToAssets(shares, _totalAssets);
}
/**
* @notice Finds the max amount of value an `owner` can remove from the cellar.
* @param owner address of the user to find max value.
* @param inShares if false, then returns value in terms of assets
* if true then returns value in terms of shares
*/
function _findMax(address owner, bool inShares) internal view returns (uint256 maxOut) {
// Check if owner shares are locked, return 0 if so.
uint256 lockTime = userShareLockStartTime[owner];
if (lockTime != 0) {
uint256 timeSharesAreUnlocked = lockTime + shareLockPeriod;
if (timeSharesAreUnlocked > block.timestamp) return 0;
}
// Get amount of assets to withdraw.
uint256 _totalAssets = _accounting(false);
uint256 assets = _convertToAssets(balanceOf[owner], _totalAssets);
uint256 withdrawable = _accounting(true);
maxOut = assets <= withdrawable ? assets : withdrawable;
if (inShares) maxOut = _convertToShares(maxOut, _totalAssets);
// else leave maxOut in terms of assets.
}
/**
* @notice Returns the max amount withdrawable by a user inclusive of performance fees
* @dev EIP4626 states maxWithdraw must not revert, but it is possible for `totalAssets` to revert
* so it does NOT conform to ERC4626 standards.
* @param owner address to check maxWithdraw of.
* @return the max amount of assets withdrawable by `owner`.
*/
function maxWithdraw(address owner) public view override returns (uint256) {
require(locked == 1, "REENTRANCY");
return _findMax(owner, false);
}
/**
* @notice Returns the max amount shares redeemable by a user
* @dev EIP4626 states maxRedeem must not revert, but it is possible for `totalAssets` to revert
* so it does NOT conform to ERC4626 standards.
* @param owner address to check maxRedeem of.
* @return the max amount of shares redeemable by `owner`.
*/
function maxRedeem(address owner) public view override returns (uint256) {
require(locked == 1, "REENTRANCY");
return _findMax(owner, true);
}
/**
* @dev Used to more efficiently convert amount of shares to assets using a stored `totalAssets` value.
*/
function _convertToAssets(uint256 shares, uint256 _totalAssets) internal view returns (uint256 assets) {
uint256 totalShares = totalSupply;
assets = totalShares == 0
? shares.changeDecimals(18, asset.decimals())
: shares.mulDivDown(_totalAssets, totalShares);
}
/**
* @dev Used to more efficiently convert amount of assets to shares using a stored `totalAssets` value.
*/
function _convertToShares(uint256 assets, uint256 _totalAssets) internal view returns (uint256 shares) {
uint256 totalShares = totalSupply;
shares = totalShares == 0
? assets.changeDecimals(asset.decimals(), 18)
: assets.mulDivDown(totalShares, _totalAssets);
}
/**
* @dev Used to more efficiently simulate minting shares using a stored `totalAssets` value.
*/
function _previewMint(uint256 shares, uint256 _totalAssets) internal view returns (uint256 assets) {
uint256 totalShares = totalSupply;
assets = totalShares == 0
? shares.changeDecimals(18, asset.decimals())
: shares.mulDivUp(_totalAssets, totalShares);
}
/**
* @dev Used to more efficiently simulate withdrawing assets using a stored `totalAssets` value.
*/
function _previewWithdraw(uint256 assets, uint256 _totalAssets) internal view returns (uint256 shares) {
uint256 totalShares = totalSupply;
shares = totalShares == 0
? assets.changeDecimals(asset.decimals(), 18)
: assets.mulDivUp(totalShares, _totalAssets);
}
// =========================================== ADAPTOR LOGIC ===========================================
/**
* @notice Emitted on when the rebalance deviation is changed.
* @param oldDeviation the old rebalance deviation
* @param newDeviation the new rebalance deviation
*/
event RebalanceDeviationChanged(uint256 oldDeviation, uint256 newDeviation);
/**
* @notice totalAssets deviated outside the range set by `allowedRebalanceDeviation`.
* @param assets the total assets in the cellar
* @param min the minimum allowed assets
* @param max the maximum allowed assets
*/
error Cellar__TotalAssetDeviatedOutsideRange(uint256 assets, uint256 min, uint256 max);
/**
* @notice Total shares in a cellar changed when they should stay constant.
* @param current the current amount of total shares
* @param expected the expected amount of total shares
*/
error Cellar__TotalSharesMustRemainConstant(uint256 current, uint256 expected);
/**
* @notice Total shares in a cellar changed when they should stay constant.
* @param requested the requested rebalance deviation
* @param max the max rebalance deviation.
*/
error Cellar__InvalidRebalanceDeviation(uint256 requested, uint256 max);
/**
* @notice Strategist attempted to use an adaptor that was not set up to be used with this cellar.
* @param adaptor the adaptor address that is not set up
*/
error Cellar__AdaptorNotSetUp(address adaptor);
/**
* @notice Maps an address to a bool indicating whether or not an adaptor
* has been set up to be used with this cellar.
*/
mapping(address => bool) public isAdaptorSetup;
/**
* @notice Allows owner to add new adaptors for the cellar to use.
*/
function setupAdaptor(address _adaptor) external onlyOwner {
// Following call reverts if adaptor does not exist, or if it does not meet cellars risk appetite.
registry.cellarSetupAdaptor(_adaptor, assetRiskTolerance, protocolRiskTolerance);
isAdaptorSetup[_adaptor] = true;
}
/**
* @notice Stores the max possible rebalance deviation for this cellar.
*/
uint64 public constant MAX_REBALANCE_DEVIATION = 0.1e18;
/**
* @notice The percent the total assets of a cellar may deviate during a `callOnAdaptor`(rebalance) call.
*/
uint256 public allowedRebalanceDeviation = 0.0003e18;
/**
* @notice Allows governance to change this cellars rebalance deviation.
* @param newDeviation the new rebalance deviation value.
*/
function setRebalanceDeviation(uint256 newDeviation) external onlyOwner {
if (newDeviation > MAX_REBALANCE_DEVIATION)
revert Cellar__InvalidRebalanceDeviation(newDeviation, MAX_REBALANCE_DEVIATION);
uint256 oldDeviation = allowedRebalanceDeviation;
allowedRebalanceDeviation = newDeviation;
emit RebalanceDeviationChanged(oldDeviation, newDeviation);
}
// Set to true before any adaptor calls are made.
/**
* @notice This bool is used to stop strategists from abusing Base Adaptor functions(deposit/withdraw).
*/
bool public blockExternalReceiver;
/**
* @notice Struct used to make calls to adaptors.
* @param adaptor the address of the adaptor to make calls to
* @param the abi encoded function calls to make to the `adaptor`
*/
struct AdaptorCall {
address adaptor;
bytes[] callData;
}
/**
* @notice Allows strategists to manage their Cellar using arbitrary logic calls to adaptors.
* @dev There are several safety checks in this function to prevent strategists from abusing it.
* - `blockExternalReceiver`
* - `totalAssets` must not change by much
* - `totalShares` must remain constant
* - adaptors must be set up to be used with this cellar
* @dev Since `totalAssets` is allowed to deviate slightly, strategists could abuse this by sending
* multiple `callOnAdaptor` calls rapidly, to gradually change the share price.
* To mitigate this, rate limiting will be put in place on the Sommelier side.
*/
function callOnAdaptor(AdaptorCall[] memory data) external onlyOwner whenNotShutdown nonReentrant {
blockExternalReceiver = true;
// Record `totalAssets` and `totalShares` before making any external calls.
uint256 minimumAllowedAssets;
uint256 maximumAllowedAssets;
uint256 totalShares;
{
uint256 assetsBeforeAdaptorCall = _accounting(false);
minimumAllowedAssets = assetsBeforeAdaptorCall.mulDivUp((1e18 - allowedRebalanceDeviation), 1e18);
maximumAllowedAssets = assetsBeforeAdaptorCall.mulDivUp((1e18 + allowedRebalanceDeviation), 1e18);
totalShares = totalSupply;
}
// Run all adaptor calls.
for (uint8 i = 0; i < data.length; ++i) {
address adaptor = data[i].adaptor;
if (!isAdaptorSetup[adaptor]) revert Cellar__AdaptorNotSetUp(adaptor);
for (uint8 j = 0; j < data[i].callData.length; j++) {
adaptor.functionDelegateCall(data[i].callData[j]);
}
}
// After making every external call, check that the totalAssets haas not deviated significantly, and that totalShares is the same.
uint256 assets = _accounting(false);
if (assets < minimumAllowedAssets || assets > maximumAllowedAssets) {
revert Cellar__TotalAssetDeviatedOutsideRange(assets, minimumAllowedAssets, maximumAllowedAssets);
}
if (totalShares != totalSupply) revert Cellar__TotalSharesMustRemainConstant(totalSupply, totalShares);
blockExternalReceiver = false;
}
// ========================================= Aave Flash Loan Support =========================================
/**
* @notice External contract attempted to initiate a flash loan.
*/
error Cellar__ExternalInitiator();
/**
* @notice executeOperation was not called by the Aave Pool.
*/
error Cellar__CallerNotAavePool();
/**
* @notice The Aave V2 Pool contract on Ethereum Mainnet.
*/
address public aavePool = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9;
/**
* @notice Allows strategist to utilize Aave V2 flashloans while rebalancing the cellar.
*/
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool) {
if (initiator != address(this)) revert Cellar__ExternalInitiator();
if (msg.sender != aavePool) revert Cellar__CallerNotAavePool();
AdaptorCall[] memory data = abi.decode(params, (AdaptorCall[]));
// Run all adaptor calls.
for (uint8 i = 0; i < data.length; ++i) {
address adaptor = data[i].adaptor;
if (!isAdaptorSetup[adaptor]) revert Cellar__AdaptorNotSetUp(adaptor);
for (uint8 j = 0; j < data[i].callData.length; j++) {
adaptor.functionDelegateCall(data[i].callData[j]);
}
}
// Approve pool to repay all debt.
for (uint256 i = 0; i < amounts.length; ++i) {
ERC20(assets[i]).safeApprove(aavePool, (amounts[i] + premiums[i]));
}
return true;
}
// ============================================ LIMITS LOGIC ============================================
/**
* @notice Total amount of assets that can be deposited for a user.
* @return assets maximum amount of assets that can be deposited
*/
function maxDeposit(address) public view override returns (uint256) {
if (isShutdown) return 0;
return type(uint256).max;
}
/**
* @notice Total amount of shares that can be minted for a user.
* @return shares maximum amount of shares that can be minted
*/
function maxMint(address) public view override returns (uint256) {
if (isShutdown) return 0;
return type(uint256).max;
}
// ========================================= FEES LOGIC =========================================
/**
* @notice Attempted to send fee shares to strategist payout address, when address is not set.
*/
error Cellar__PayoutNotSet();
/**
* @dev Calculate the amount of fees to mint such that value of fees after minting is not diluted.
*/
function _convertToFees(uint256 feesInShares) internal view returns (uint256 fees) {
// Saves an SLOAD.
uint256 totalShares = totalSupply;
// Get the amount of fees to mint. Without this, the value of fees minted would be slightly
// diluted because total shares increased while total assets did not. This counteracts that.
if (totalShares > feesInShares) {
// Denominator is greater than zero
uint256 denominator = totalShares - feesInShares;
fees = feesInShares.mulDivUp(totalShares, denominator);
}
// If denominator is less than or equal to zero, `fees` should be zero.
}
/**
* @notice Emitted when platform fees are send to the Sommelier chain.
* @param feesInSharesRedeemed amount of fees redeemed for assets to send
* @param feesInAssetsSent amount of assets fees were redeemed for that were sent
*/
event SendFees(uint256 feesInSharesRedeemed, uint256 feesInAssetsSent);
/**
* @notice Transfer accrued fees to the Sommelier chain to distribute.
* @dev Fees are accrued as shares and redeemed upon transfer.
* @dev assumes cellar's accounting asset is able to be transferred and sent to Cosmos
*/
function sendFees() external nonReentrant {
address strategistPayoutAddress = feeData.strategistPayoutAddress;
if (strategistPayoutAddress == address(0)) revert Cellar__PayoutNotSet();
uint256 _totalAssets = _accounting(false);
// Calculate platform fees earned.
uint256 elapsedTime = block.timestamp - feeData.lastAccrual;
uint256 platformFeeInAssets = (_totalAssets * elapsedTime * feeData.platformFee) / 1e18 / 365 days;
uint256 platformFees = _convertToFees(_convertToShares(platformFeeInAssets, _totalAssets));
_mint(address(this), platformFees);
uint256 strategistFeeSharesDue = platformFees.mulWadDown(feeData.strategistPlatformCut);
if (strategistFeeSharesDue > 0) {
//transfer shares to strategist
// Take from Solmate ERC20.sol
{
balanceOf[address(this)] -= strategistFeeSharesDue;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[strategistPayoutAddress] += strategistFeeSharesDue;
}
emit Transfer(address(this), strategistPayoutAddress, strategistFeeSharesDue);
}
// _transfer(address(this), strategistPayoutAddress, strategistFeeSharesDue);
platformFees -= strategistFeeSharesDue;
}
feeData.lastAccrual = uint32(block.timestamp);
// Redeem our fee shares for assets to send to the fee distributor module.
uint256 assets = _convertToAssets(platformFees, _totalAssets);
if (assets > 0) {
_burn(address(this), platformFees);
// Transfer assets to a fee distributor on the Sommelier chain.
IGravity gravityBridge = IGravity(registry.getAddress(GRAVITY_BRIDGE_REGISTRY_SLOT));
asset.safeApprove(address(gravityBridge), assets);
gravityBridge.sendToCosmos(address(asset), registry.feesDistributor(), assets);
}
emit SendFees(platformFees, assets);
}
// ========================================== HELPER FUNCTIONS ==========================================
/**
* @dev Deposit into a position according to its position type and update related state.
* @param position address to deposit funds into
* @param assets the amount of assets to deposit into the position
*/
function _depositTo(uint32 position, uint256 assets) internal {
address adaptor = getPositionData[position].adaptor;
adaptor.functionDelegateCall(
abi.encodeWithSelector(
BaseAdaptor.deposit.selector,
assets,
getPositionData[position].adaptorData,
getPositionData[position].configurationData
)
);
}
/**
* @dev Withdraw from a position according to its position type and update related state.
* @param position address to withdraw funds from
* @param assets the amount of assets to withdraw from the position
* @param receiver the address to sent withdrawn assets to
*/
function _withdrawFrom(
uint32 position,
uint256 assets,
address receiver
) internal {
address adaptor = getPositionData[position].adaptor;
adaptor.functionDelegateCall(
abi.encodeWithSelector(
BaseAdaptor.withdraw.selector,
assets,
receiver,
getPositionData[position].adaptorData,
getPositionData[position].configurationData
)
);
}
/**
* @dev Get the withdrawable balance of a position according to its position type.
* @param position position to get the withdrawable balance of
*/
function _withdrawableFrom(uint32 position) internal view returns (uint256) {
// Debt positions always return 0 for their withdrawable.
if (getPositionData[position].isDebt) return 0;
return
BaseAdaptor(getPositionData[position].adaptor).withdrawableFrom(
getPositionData[position].adaptorData,
getPositionData[position].configurationData
);
}
/**
* @dev Get the balance of a position according to its position type.
* @dev For ERC4626 position balances, this uses `previewRedeem` as opposed
* to `convertToAssets` so that balanceOf ERC4626 positions includes fees taken on withdraw.
* @param position position to get the balance of
*/
function _balanceOf(uint32 position) internal view returns (uint256) {
address adaptor = getPositionData[position].adaptor;
return BaseAdaptor(adaptor).balanceOf(getPositionData[position].adaptorData);
}
/**
* @dev Get the asset of a position according to its position type.
* @param position to get the asset of
*/
function _assetOf(uint32 position) internal view returns (ERC20) {
address adaptor = getPositionData[position].adaptor;
return BaseAdaptor(adaptor).assetOf(getPositionData[position].adaptorData);
}
/**
* @notice Get all the credit positions underlying assets.
*/
function getPositionAssets() external view returns (ERC20[] memory assets) {
assets = new ERC20[](creditPositions.length);
for (uint256 i = 0; i < creditPositions.length; ++i) {
assets[i] = _assetOf(creditPositions[i]);
}
}
}// 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 decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal INITIAL_CHAIN_ID;
bytes32 internal 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 "src/base/ERC20.sol";
import { SafeTransferLib } from "src/base/SafeTransferLib.sol";
import { Math } from "src/utils/Math.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using Math 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 asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol,
uint8 _decimals
) ERC20(_name, _symbol, _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");
beforeDeposit(assets, shares, receiver);
// 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, receiver);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
beforeDeposit(assets, shares, receiver);
// 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, receiver);
}
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, receiver, owner);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
afterWithdraw(assets, shares, receiver, owner);
}
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, receiver, owner);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
afterWithdraw(assets, shares, receiver, owner);
}
/*//////////////////////////////////////////////////////////////
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 beforeDeposit(
uint256 assets,
uint256 shares,
address receiver
) internal virtual {}
function afterDeposit(
uint256 assets,
uint256 shares,
address receiver
) internal virtual {}
function beforeWithdraw(
uint256 assets,
uint256 shares,
address receiver,
address owner
) internal virtual {}
function afterWithdraw(
uint256 assets,
uint256 shares,
address receiver,
address owner
) internal virtual {}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
import { IMulticall } from "src/interfaces/IMulticall.sol";
/**
* @title Multicall
* @notice Enables calling multiple methods in a single call to the contract
* From: https://github.com/Uniswap/v3-periphery/blob/1d69caf0d6c8cfeae9acd1f34ead30018d6e6400/contracts/base/Multicall.sol
*/
abstract contract Multicall is IMulticall {
/// @inheritdoc IMulticall
function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
// solhint-disable-next-line reason-string
if (result.length < 68) revert();
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}
results[i] = result;
}
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import { ERC20 } from "src/base/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;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
/// @title Multicall interface
/// @notice Enables calling multiple methods in a single call to the contract
// From: https://github.com/Uniswap/v3-periphery/contracts/interfaces/IMulticall.sol
interface IMulticall {
/// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
/// @dev The `msg.value` should not be trusted for any method callable from multicall.
/// @param data The encoded function data for each of the calls to make to this contract
/// @return results The results from each of the calls passed in via data
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.16;
library DataTypes {
struct ReserveData {
//stores the reserve configuration
ReserveConfigurationMap configuration;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate;
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex;
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate;
//the current stable borrow rate. Expressed in ray
uint128 currentStableBorrowRate;
//timestamp of last update
uint40 lastUpdateTimestamp;
//the id of the reserve. Represents the position in the list of the active reserves
uint16 id;
//aToken address
address aTokenAddress;
//stableDebtToken address
address stableDebtTokenAddress;
//variableDebtToken address
address variableDebtTokenAddress;
//address of the interest rate strategy
address interestRateStrategyAddress;
//the current treasury balance, scaled
uint128 accruedToTreasury;
//the outstanding unbacked aTokens minted through the bridging feature
uint128 unbacked;
//the outstanding debt borrowed against this asset in isolation mode
uint128 isolationModeTotalDebt;
}
struct ReserveConfigurationMap {
//bit 0-15: LTV
//bit 16-31: Liq. threshold
//bit 32-47: Liq. bonus
//bit 48-55: Decimals
//bit 56: reserve is active
//bit 57: reserve is frozen
//bit 58: borrowing is enabled
//bit 59: stable rate borrowing enabled
//bit 60: asset is paused
//bit 61: borrowing in isolation mode is enabled
//bit 62-63: reserved
//bit 64-79: reserve factor
//bit 80-115 borrow cap in whole tokens, borrowCap == 0 => no cap
//bit 116-151 supply cap in whole tokens, supplyCap == 0 => no cap
//bit 152-167 liquidation protocol fee
//bit 168-175 eMode category
//bit 176-211 unbacked mint cap in whole tokens, unbackedMintCap == 0 => minting disabled
//bit 212-251 debt ceiling for isolation mode with (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals
//bit 252-255 unused
uint256 data;
}
struct UserConfigurationMap {
/**
* @dev Bitmap of the users collaterals and borrows. It is divided in pairs of bits, one pair per asset.
* The first bit indicates if an asset is used as collateral by the user, the second whether an
* asset is borrowed by the user.
*/
uint256 data;
}
struct EModeCategory {
// each eMode category has a custom ltv and liquidation threshold
uint16 ltv;
uint16 liquidationThreshold;
uint16 liquidationBonus;
// each eMode category may or may not have a custom oracle to override the individual assets price oracles
address priceSource;
string label;
}
enum InterestRateMode {
NONE,
STABLE,
VARIABLE
}
struct ReserveCache {
uint256 currScaledVariableDebt;
uint256 nextScaledVariableDebt;
uint256 currPrincipalStableDebt;
uint256 currAvgStableBorrowRate;
uint256 currTotalStableDebt;
uint256 nextAvgStableBorrowRate;
uint256 nextTotalStableDebt;
uint256 currLiquidityIndex;
uint256 nextLiquidityIndex;
uint256 currVariableBorrowIndex;
uint256 nextVariableBorrowIndex;
uint256 currLiquidityRate;
uint256 currVariableBorrowRate;
uint256 reserveFactor;
ReserveConfigurationMap reserveConfiguration;
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
uint40 reserveLastUpdateTimestamp;
uint40 stableDebtLastUpdateTimestamp;
}
struct ExecuteLiquidationCallParams {
uint256 reservesCount;
uint256 debtToCover;
address collateralAsset;
address debtAsset;
address user;
bool receiveAToken;
address priceOracle;
uint8 userEModeCategory;
address priceOracleSentinel;
}
struct ExecuteSupplyParams {
address asset;
uint256 amount;
address onBehalfOf;
uint16 referralCode;
}
struct ExecuteBorrowParams {
address asset;
address user;
address onBehalfOf;
uint256 amount;
InterestRateMode interestRateMode;
uint16 referralCode;
bool releaseUnderlying;
uint256 maxStableRateBorrowSizePercent;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
address priceOracleSentinel;
}
struct ExecuteRepayParams {
address asset;
uint256 amount;
InterestRateMode interestRateMode;
address onBehalfOf;
bool useATokens;
}
struct ExecuteWithdrawParams {
address asset;
uint256 amount;
address to;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
}
struct ExecuteSetUserEModeParams {
uint256 reservesCount;
address oracle;
uint8 categoryId;
}
struct FinalizeTransferParams {
address asset;
address from;
address to;
uint256 amount;
uint256 balanceFromBefore;
uint256 balanceToBefore;
uint256 reservesCount;
address oracle;
uint8 fromEModeCategory;
}
struct FlashloanParams {
address receiverAddress;
address[] assets;
uint256[] amounts;
uint256[] interestRateModes;
address onBehalfOf;
bytes params;
uint16 referralCode;
uint256 flashLoanPremiumToProtocol;
uint256 flashLoanPremiumTotal;
uint256 maxStableRateBorrowSizePercent;
uint256 reservesCount;
address addressesProvider;
uint8 userEModeCategory;
bool isAuthorizedFlashBorrower;
}
struct FlashloanSimpleParams {
address receiverAddress;
address asset;
uint256 amount;
bytes params;
uint16 referralCode;
uint256 flashLoanPremiumToProtocol;
uint256 flashLoanPremiumTotal;
}
struct FlashLoanRepaymentParams {
uint256 amount;
uint256 totalPremium;
uint256 flashLoanPremiumToProtocol;
address asset;
address receiverAddress;
uint16 referralCode;
}
struct CalculateUserAccountDataParams {
UserConfigurationMap userConfig;
uint256 reservesCount;
address user;
address oracle;
uint8 userEModeCategory;
}
struct ValidateBorrowParams {
ReserveCache reserveCache;
UserConfigurationMap userConfig;
address asset;
address userAddress;
uint256 amount;
InterestRateMode interestRateMode;
uint256 maxStableLoanPercent;
uint256 reservesCount;
address oracle;
uint8 userEModeCategory;
address priceOracleSentinel;
bool isolationModeActive;
address isolationModeCollateralAddress;
uint256 isolationModeDebtCeiling;
}
struct ValidateLiquidationCallParams {
ReserveCache debtReserveCache;
uint256 totalDebt;
uint256 healthFactor;
address priceOracleSentinel;
}
struct CalculateInterestRatesParams {
uint256 unbacked;
uint256 liquidityAdded;
uint256 liquidityTaken;
uint256 totalStableDebt;
uint256 totalVariableDebt;
uint256 averageStableBorrowRate;
uint256 reserveFactor;
address reserve;
address aToken;
}
struct InitReserveParams {
address asset;
address aTokenAddress;
address stableDebtAddress;
address variableDebtAddress;
address interestRateStrategyAddress;
uint16 reservesCount;
uint16 maxNumberReserves;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IAaveToken {
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol";
interface IChainlinkAggregator is AggregatorV2V3Interface {
function maxAnswer() external view returns (int192);
function minAnswer() external view returns (int192);
function aggregator() external view returns (address);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
interface ICurvePool {
function coins(uint256 i) external view returns (address);
function get_virtual_price() external view returns (uint256);
function claim_admin_fees() external; // For USDT/WETH/WBTC
function withdraw_admin_fees() external;
function gamma() external view returns (uint256);
function A() external view returns (uint256);
function lp_price() external view returns (uint256);
function price_oracle() external view returns (uint256);
function price_oracle(uint256 i) external view returns (uint256);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
interface IGravity {
function sendToCosmos(
address _tokenContract,
bytes32 _destination,
uint256 _amount
) external;
}// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.16;
import { IPoolAddressesProvider } from "./IPoolAddressesProvider.sol";
import { DataTypes } from "./DataTypes.sol";
/**
* @title IPool
* @author Aave
* @notice Defines the basic interface for an Aave Pool.
**/
interface IPool {
/**
* @dev Emitted on mintUnbacked()
* @param reserve The address of the underlying asset of the reserve
* @param user The address initiating the supply
* @param onBehalfOf The beneficiary of the supplied assets, receiving the aTokens
* @param amount The amount of supplied assets
* @param referralCode The referral code used
**/
event MintUnbacked(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint16 indexed referralCode
);
/**
* @dev Emitted on backUnbacked()
* @param reserve The address of the underlying asset of the reserve
* @param backer The address paying for the backing
* @param amount The amount added as backing
* @param fee The amount paid in fees
**/
event BackUnbacked(address indexed reserve, address indexed backer, uint256 amount, uint256 fee);
/**
* @dev Emitted on supply()
* @param reserve The address of the underlying asset of the reserve
* @param user The address initiating the supply
* @param onBehalfOf The beneficiary of the supply, receiving the aTokens
* @param amount The amount supplied
* @param referralCode The referral code used
**/
event Supply(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint16 indexed referralCode
);
/**
* @dev Emitted on withdraw()
* @param reserve The address of the underlying asset being withdrawn
* @param user The address initiating the withdrawal, owner of aTokens
* @param to The address that will receive the underlying
* @param amount The amount to be withdrawn
**/
event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount);
/**
* @dev Emitted on borrow() and flashLoan() when debt needs to be opened
* @param reserve The address of the underlying asset being borrowed
* @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just
* initiator of the transaction on flashLoan()
* @param onBehalfOf The address that will be getting the debt
* @param amount The amount borrowed out
* @param interestRateMode The rate mode: 1 for Stable, 2 for Variable
* @param borrowRate The numeric rate at which the user has borrowed, expressed in ray
* @param referralCode The referral code used
**/
event Borrow(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
DataTypes.InterestRateMode interestRateMode,
uint256 borrowRate,
uint16 indexed referralCode
);
/**
* @dev Emitted on repay()
* @param reserve The address of the underlying asset of the reserve
* @param user The beneficiary of the repayment, getting his debt reduced
* @param repayer The address of the user initiating the repay(), providing the funds
* @param amount The amount repaid
* @param useATokens True if the repayment is done using aTokens, `false` if done with underlying asset directly
**/
event Repay(
address indexed reserve,
address indexed user,
address indexed repayer,
uint256 amount,
bool useATokens
);
/**
* @dev Emitted on swapBorrowRateMode()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user swapping his rate mode
* @param interestRateMode The current interest rate mode of the position being swapped: 1 for Stable, 2 for Variable
**/
event SwapBorrowRateMode(
address indexed reserve,
address indexed user,
DataTypes.InterestRateMode interestRateMode
);
/**
* @dev Emitted on borrow(), repay() and liquidationCall() when using isolated assets
* @param asset The address of the underlying asset of the reserve
* @param totalDebt The total isolation mode debt for the reserve
*/
event IsolationModeTotalDebtUpdated(address indexed asset, uint256 totalDebt);
/**
* @dev Emitted when the user selects a certain asset category for eMode
* @param user The address of the user
* @param categoryId The category id
**/
event UserEModeSet(address indexed user, uint8 categoryId);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
**/
event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
**/
event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on rebalanceStableBorrowRate()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user for which the rebalance has been executed
**/
event RebalanceStableBorrowRate(address indexed reserve, address indexed user);
/**
* @dev Emitted on flashLoan()
* @param target The address of the flash loan receiver contract
* @param initiator The address initiating the flash loan
* @param asset The address of the asset being flash borrowed
* @param amount The amount flash borrowed
* @param interestRateMode The flashloan mode: 0 for regular flashloan, 1 for Stable debt, 2 for Variable debt
* @param premium The fee flash borrowed
* @param referralCode The referral code used
**/
event FlashLoan(
address indexed target,
address initiator,
address indexed asset,
uint256 amount,
DataTypes.InterestRateMode interestRateMode,
uint256 premium,
uint16 indexed referralCode
);
/**
* @dev Emitted when a borrower is liquidated.
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param liquidatedCollateralAmount The amount of collateral received by the liquidator
* @param liquidator The address of the liquidator
* @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
**/
event LiquidationCall(
address indexed collateralAsset,
address indexed debtAsset,
address indexed user,
uint256 debtToCover,
uint256 liquidatedCollateralAmount,
address liquidator,
bool receiveAToken
);
/**
* @dev Emitted when the state of a reserve is updated.
* @param reserve The address of the underlying asset of the reserve
* @param liquidityRate The next liquidity rate
* @param stableBorrowRate The next stable borrow rate
* @param variableBorrowRate The next variable borrow rate
* @param liquidityIndex The next liquidity index
* @param variableBorrowIndex The next variable borrow index
**/
event ReserveDataUpdated(
address indexed reserve,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
/**
* @dev Emitted when the protocol treasury receives minted aTokens from the accrued interest.
* @param reserve The address of the reserve
* @param amountMinted The amount minted to the treasury
**/
event MintedToTreasury(address indexed reserve, uint256 amountMinted);
/**
* @dev Mints an `amount` of aTokens to the `onBehalfOf`
* @param asset The address of the underlying asset to mint
* @param amount The amount to mint
* @param onBehalfOf The address that will receive the aTokens
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function mintUnbacked(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @dev Back the current unbacked underlying with `amount` and pay `fee`.
* @param asset The address of the underlying asset to back
* @param amount The amount to back
* @param fee The amount paid in fees
**/
function backUnbacked(
address asset,
uint256 amount,
uint256 fee
) external;
/**
* @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User supplies 100 USDC and gets in return 100 aUSDC
* @param asset The address of the underlying asset to supply
* @param amount The amount to be supplied
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function supply(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @notice Supply with transfer approval of asset to be supplied done via permit function
* see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
* @param asset The address of the underlying asset to supply
* @param amount The amount to be supplied
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param deadline The deadline timestamp that the permit is valid
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
* @param permitV The V parameter of ERC712 permit sig
* @param permitR The R parameter of ERC712 permit sig
* @param permitS The S parameter of ERC712 permit sig
**/
function supplyWithPermit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode,
uint256 deadline,
uint8 permitV,
bytes32 permitR,
bytes32 permitS
) external;
/**
* @notice Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to The address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
* @return The final amount withdrawn
**/
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
/**
* @notice Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower
* already supplied enough collateral, or he was given enough allowance by a credit delegator on the
* corresponding debt token (StableDebtToken or VariableDebtToken)
* - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet
* and 100 stable/variable debt tokens, depending on the `interestRateMode`
* @param asset The address of the underlying asset to borrow
* @param amount The amount to be borrowed
* @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable
* @param referralCode The code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
* @param onBehalfOf The address of the user who will receive the debt. Should be the address of the borrower itself
* calling the function if he wants to borrow against his own collateral, or the address of the credit delegator
* if he has been given credit delegation allowance
**/
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode,
address onBehalfOf
) external;
/**
* @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned
* - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param interestRateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
* @param onBehalfOf The address of the user who will get his debt reduced/removed. Should be the address of the
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
* other borrower whose debt should be removed
* @return The final amount repaid
**/
function repay(
address asset,
uint256 amount,
uint256 interestRateMode,
address onBehalfOf
) external returns (uint256);
/**
* @notice Repay with transfer approval of asset to be repaid done via permit function
* see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param interestRateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
* @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
* other borrower whose debt should be removed
* @param deadline The deadline timestamp that the permit is valid
* @param permitV The V parameter of ERC712 permit sig
* @param permitR The R parameter of ERC712 permit sig
* @param permitS The S parameter of ERC712 permit sig
* @return The final amount repaid
**/
function repayWithPermit(
address asset,
uint256 amount,
uint256 interestRateMode,
address onBehalfOf,
uint256 deadline,
uint8 permitV,
bytes32 permitR,
bytes32 permitS
) external returns (uint256);
/**
* @notice Repays a borrowed `amount` on a specific reserve using the reserve aTokens, burning the
* equivalent debt tokens
* - E.g. User repays 100 USDC using 100 aUSDC, burning 100 variable/stable debt tokens
* @dev Passing uint256.max as amount will clean up any residual aToken dust balance, if the user aToken
* balance is not enough to cover the whole debt
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param interestRateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
* @return The final amount repaid
**/
function repayWithATokens(
address asset,
uint256 amount,
uint256 interestRateMode
) external returns (uint256);
/**
* @notice Allows a borrower to swap his debt between stable and variable mode, or vice versa
* @param asset The address of the underlying asset borrowed
* @param interestRateMode The current interest rate mode of the position being swapped: 1 for Stable, 2 for Variable
**/
function swapBorrowRateMode(address asset, uint256 interestRateMode) external;
/**
* @notice Rebalances the stable interest rate of a user to the current stable rate defined on the reserve.
* - Users can be rebalanced if the following conditions are satisfied:
* 1. Usage ratio is above 95%
* 2. the current supply APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too
* much has been borrowed at a stable rate and suppliers are not earning enough
* @param asset The address of the underlying asset borrowed
* @param user The address of the user to be rebalanced
**/
function rebalanceStableBorrowRate(address asset, address user) external;
/**
* @notice Allows suppliers to enable/disable a specific supplied asset as collateral
* @param asset The address of the underlying asset supplied
* @param useAsCollateral True if the user wants to use the supply as collateral, false otherwise
**/
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;
/**
* @notice Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
* - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
* a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
**/
function liquidationCall(
address collateralAsset,
address debtAsset,
address user,
uint256 debtToCover,
bool receiveAToken
) external;
/**
* @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
* as long as the amount taken plus a fee is returned.
* @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
* into consideration. For further details please visit https://developers.aave.com
* @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanReceiver interface
* @param assets The addresses of the assets being flash-borrowed
* @param amounts The amounts of the assets being flash-borrowed
* @param interestRateModes Types of the debt to open if the flash loan is not returned:
* 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
* 1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
* 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
* @param onBehalfOf The address that will receive the debt in the case of using on `modes` 1 or 2
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode The code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata interestRateModes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
/**
* @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
* as long as the amount taken plus a fee is returned.
* @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
* into consideration. For further details please visit https://developers.aave.com
* @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanSimpleReceiver interface
* @param asset The address of the asset being flash-borrowed
* @param amount The amount of the asset being flash-borrowed
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode The code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function flashLoanSimple(
address receiverAddress,
address asset,
uint256 amount,
bytes calldata params,
uint16 referralCode
) external;
/**
* @notice Returns the user account data across all the reserves
* @param user The address of the user
* @return totalCollateralBase The total collateral of the user in the base currency used by the price feed
* @return totalDebtBase The total debt of the user in the base currency used by the price feed
* @return availableBorrowsBase The borrowing power left of the user in the base currency used by the price feed
* @return currentLiquidationThreshold The liquidation threshold of the user
* @return ltv The loan to value of The user
* @return healthFactor The current health factor of the user
**/
function getUserAccountData(address user)
external
view
returns (
uint256 totalCollateralBase,
uint256 totalDebtBase,
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
/**
* @notice Initializes a reserve, activating it, assigning an aToken and debt tokens and an
* interest rate strategy
* @dev Only callable by the PoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
* @param aTokenAddress The address of the aToken that will be assigned to the reserve
* @param stableDebtAddress The address of the StableDebtToken that will be assigned to the reserve
* @param variableDebtAddress The address of the VariableDebtToken that will be assigned to the reserve
* @param interestRateStrategyAddress The address of the interest rate strategy contract
**/
function initReserve(
address asset,
address aTokenAddress,
address stableDebtAddress,
address variableDebtAddress,
address interestRateStrategyAddress
) external;
/**
* @notice Drop a reserve
* @dev Only callable by the PoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
**/
function dropReserve(address asset) external;
/**
* @notice Updates the address of the interest rate strategy contract
* @dev Only callable by the PoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
* @param rateStrategyAddress The address of the interest rate strategy contract
**/
function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress) external;
/**
* @notice Sets the configuration bitmap of the reserve as a whole
* @dev Only callable by the PoolConfigurator contract
* @param asset The address of the underlying asset of the reserve
* @param configuration The new configuration bitmap
**/
function setConfiguration(address asset, DataTypes.ReserveConfigurationMap calldata configuration) external;
/**
* @notice Returns the configuration of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The configuration of the reserve
**/
function getConfiguration(address asset) external view returns (DataTypes.ReserveConfigurationMap memory);
/**
* @notice Returns the configuration of the user across all the reserves
* @param user The user address
* @return The configuration of the user
**/
function getUserConfiguration(address user) external view returns (DataTypes.UserConfigurationMap memory);
/**
* @notice Returns the normalized income normalized income of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The reserve's normalized income
*/
function getReserveNormalizedIncome(address asset) external view returns (uint256);
/**
* @notice Returns the normalized variable debt per unit of asset
* @param asset The address of the underlying asset of the reserve
* @return The reserve normalized variable debt
*/
function getReserveNormalizedVariableDebt(address asset) external view returns (uint256);
/**
* @notice Returns the state and configuration of the reserve
* @param asset The address of the underlying asset of the reserve
**/
function getReserveData(address asset)
external
view
returns (
uint256 availableLiquidity,
uint256 totalStableDebt,
uint256 totalVariableDebt,
uint256 liquidityRate,
uint256 variableBorrowRate,
uint256 stableBorrowRate,
uint256 averageStableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex,
uint40 lastUpdateTimestamp
);
/**
* @notice Validates and finalizes an aToken transfer
* @dev Only callable by the overlying aToken of the `asset`
* @param asset The address of the underlying asset of the aToken
* @param from The user from which the aTokens are transferred
* @param to The user receiving the aTokens
* @param amount The amount being transferred/withdrawn
* @param balanceFromBefore The aToken balance of the `from` user before the transfer
* @param balanceToBefore The aToken balance of the `to` user before the transfer
*/
function finalizeTransfer(
address asset,
address from,
address to,
uint256 amount,
uint256 balanceFromBefore,
uint256 balanceToBefore
) external;
/**
* @notice Returns the list of the underlying assets of all the initialized reserves
* @dev It does not include dropped reserves
* @return The addresses of the underlying assets of the initialized reserves
**/
function getReservesList() external view returns (address[] memory);
/**
* @notice Returns the address of the underlying asset of a reserve by the reserve id as stored in the DataTypes.ReserveData struct
* @param id The id of the reserve as stored in the DataTypes.ReserveData struct
* @return The address of the reserve associated with id
**/
function getReserveAddressById(uint16 id) external view returns (address);
/**
* @notice Returns the PoolAddressesProvider connected to this contract
* @return The address of the PoolAddressesProvider
**/
function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider);
/**
* @notice Updates the protocol fee on the bridging
* @param bridgeProtocolFee The part of the premium sent to the protocol treasury
*/
function updateBridgeProtocolFee(uint256 bridgeProtocolFee) external;
/**
* @notice Updates flash loan premiums. Flash loan premium consists of two parts:
* - A part is sent to aToken holders as extra, one time accumulated interest
* - A part is collected by the protocol treasury
* @dev The total premium is calculated on the total borrowed amount
* @dev The premium to protocol is calculated on the total premium, being a percentage of `flashLoanPremiumTotal`
* @dev Only callable by the PoolConfigurator contract
* @param flashLoanPremiumTotal The total premium, expressed in bps
* @param flashLoanPremiumToProtocol The part of the premium sent to the protocol treasury, expressed in bps
*/
function updateFlashloanPremiums(uint128 flashLoanPremiumTotal, uint128 flashLoanPremiumToProtocol) external;
/**
* @notice Configures a new category for the eMode.
* @dev In eMode, the protocol allows very high borrowing power to borrow assets of the same category.
* The category 0 is reserved as it's the default for volatile assets
* @param id The id of the category
* @param config The configuration of the category
*/
function configureEModeCategory(uint8 id, DataTypes.EModeCategory memory config) external;
/**
* @notice Returns the data of an eMode category
* @param id The id of the category
* @return The configuration data of the category
*/
function getEModeCategoryData(uint8 id) external view returns (DataTypes.EModeCategory memory);
/**
* @notice Allows a user to use the protocol in eMode
* @param categoryId The id of the category
*/
function setUserEMode(uint8 categoryId) external;
/**
* @notice Returns the eMode the user is using
* @param user The address of the user
* @return The eMode id
*/
function getUserEMode(address user) external view returns (uint256);
/**
* @notice Resets the isolation mode total debt of the given asset to zero
* @dev It requires the given asset has zero debt ceiling
* @param asset The address of the underlying asset to reset the isolationModeTotalDebt
*/
function resetIsolationModeTotalDebt(address asset) external;
/**
* @notice Returns the percentage of available liquidity that can be borrowed at once at stable rate
* @return The percentage of available liquidity to borrow, expressed in bps
*/
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() external view returns (uint256);
/**
* @notice Returns the total fee on flash loans
* @return The total fee on flashloans
*/
function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128);
/**
* @notice Returns the part of the bridge fees sent to protocol
* @return The bridge fee sent to the protocol treasury
*/
function BRIDGE_PROTOCOL_FEE() external view returns (uint256);
/**
* @notice Returns the part of the flashloan fees sent to protocol
* @return The flashloan fee sent to the protocol treasury
*/
function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint128);
/**
* @notice Returns the maximum number of reserves supported to be listed in this Pool
* @return The maximum number of reserves supported
*/
function MAX_NUMBER_RESERVES() external view returns (uint16);
/**
* @notice Mints the assets accrued through the reserve factor to the treasury in the form of aTokens
* @param assets The list of reserves for which the minting needs to be executed
**/
function mintToTreasury(address[] calldata assets) external;
/**
* @notice Rescue and transfer tokens locked in this contract
* @param token The address of the token
* @param to The address of the recipient
* @param amount The amount of token to transfer
*/
function rescueTokens(
address token,
address to,
uint256 amount
) external;
/**
* @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User supplies 100 USDC and gets in return 100 aUSDC
* @dev Deprecated: Use the `supply` function instead
* @param asset The address of the underlying asset to supply
* @param amount The amount to be supplied
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
}// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.16;
/**
* @title IPoolAddressesProvider
* @author Aave
* @notice Defines the basic interface for a Pool Addresses Provider.
**/
interface IPoolAddressesProvider {
/**
* @dev Emitted when the market identifier is updated.
* @param oldMarketId The old id of the market
* @param newMarketId The new id of the market
*/
event MarketIdSet(string indexed oldMarketId, string indexed newMarketId);
/**
* @dev Emitted when the pool is updated.
* @param oldAddress The old address of the Pool
* @param newAddress The new address of the Pool
*/
event PoolUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the pool configurator is updated.
* @param oldAddress The old address of the PoolConfigurator
* @param newAddress The new address of the PoolConfigurator
*/
event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the price oracle is updated.
* @param oldAddress The old address of the PriceOracle
* @param newAddress The new address of the PriceOracle
*/
event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the ACL manager is updated.
* @param oldAddress The old address of the ACLManager
* @param newAddress The new address of the ACLManager
*/
event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the ACL admin is updated.
* @param oldAddress The old address of the ACLAdmin
* @param newAddress The new address of the ACLAdmin
*/
event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the price oracle sentinel is updated.
* @param oldAddress The old address of the PriceOracleSentinel
* @param newAddress The new address of the PriceOracleSentinel
*/
event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the pool data provider is updated.
* @param oldAddress The old address of the PoolDataProvider
* @param newAddress The new address of the PoolDataProvider
*/
event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when a new proxy is created.
* @param id The identifier of the proxy
* @param proxyAddress The address of the created proxy contract
* @param implementationAddress The address of the implementation contract
*/
event ProxyCreated(bytes32 indexed id, address indexed proxyAddress, address indexed implementationAddress);
/**
* @dev Emitted when a new non-proxied contract address is registered.
* @param id The identifier of the contract
* @param oldAddress The address of the old contract
* @param newAddress The address of the new contract
*/
event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress);
/**
* @dev Emitted when the implementation of the proxy registered with id is updated
* @param id The identifier of the contract
* @param proxyAddress The address of the proxy contract
* @param oldImplementationAddress The address of the old implementation contract
* @param newImplementationAddress The address of the new implementation contract
*/
event AddressSetAsProxy(
bytes32 indexed id,
address indexed proxyAddress,
address oldImplementationAddress,
address indexed newImplementationAddress
);
/**
* @notice Returns the id of the Aave market to which this contract points to.
* @return The market id
**/
function getMarketId() external view returns (string memory);
/**
* @notice Associates an id with a specific PoolAddressesProvider.
* @dev This can be used to create an onchain registry of PoolAddressesProviders to
* identify and validate multiple Aave markets.
* @param newMarketId The market id
*/
function setMarketId(string calldata newMarketId) external;
/**
* @notice Returns an address by its identifier.
* @dev The returned address might be an EOA or a contract, potentially proxied
* @dev It returns ZERO if there is no registered address with the given id
* @param id The id
* @return The address of the registered for the specified id
*/
function getAddress(bytes32 id) external view returns (address);
/**
* @notice General function to update the implementation of a proxy registered with
* certain `id`. If there is no proxy registered, it will instantiate one and
* set as implementation the `newImplementationAddress`.
* @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit
* setter function, in order to avoid unexpected consequences
* @param id The id
* @param newImplementationAddress The address of the new implementation
*/
function setAddressAsProxy(bytes32 id, address newImplementationAddress) external;
/**
* @notice Sets an address for an id replacing the address saved in the addresses map.
* @dev IMPORTANT Use this function carefully, as it will do a hard replacement
* @param id The id
* @param newAddress The address to set
*/
function setAddress(bytes32 id, address newAddress) external;
/**
* @notice Returns the address of the Pool proxy.
* @return The Pool proxy address
**/
function getPool() external view returns (address);
/**
* @notice Updates the implementation of the Pool, or creates a proxy
* setting the new `pool` implementation when the function is called for the first time.
* @param newPoolImpl The new Pool implementation
**/
function setPoolImpl(address newPoolImpl) external;
/**
* @notice Returns the address of the PoolConfigurator proxy.
* @return The PoolConfigurator proxy address
**/
function getPoolConfigurator() external view returns (address);
/**
* @notice Updates the implementation of the PoolConfigurator, or creates a proxy
* setting the new `PoolConfigurator` implementation when the function is called for the first time.
* @param newPoolConfiguratorImpl The new PoolConfigurator implementation
**/
function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external;
/**
* @notice Returns the address of the price oracle.
* @return The address of the PriceOracle
*/
function getPriceOracle() external view returns (address);
/**
* @notice Updates the address of the price oracle.
* @param newPriceOracle The address of the new PriceOracle
*/
function setPriceOracle(address newPriceOracle) external;
/**
* @notice Returns the address of the ACL manager.
* @return The address of the ACLManager
*/
function getACLManager() external view returns (address);
/**
* @notice Updates the address of the ACL manager.
* @param newAclManager The address of the new ACLManager
**/
function setACLManager(address newAclManager) external;
/**
* @notice Returns the address of the ACL admin.
* @return The address of the ACL admin
*/
function getACLAdmin() external view returns (address);
/**
* @notice Updates the address of the ACL admin.
* @param newAclAdmin The address of the new ACL admin
*/
function setACLAdmin(address newAclAdmin) external;
/**
* @notice Returns the address of the price oracle sentinel.
* @return The address of the PriceOracleSentinel
*/
function getPriceOracleSentinel() external view returns (address);
/**
* @notice Updates the address of the price oracle sentinel.
* @param newPriceOracleSentinel The address of the new PriceOracleSentinel
**/
function setPriceOracleSentinel(address newPriceOracleSentinel) external;
/**
* @notice Returns the address of the data provider.
* @return The address of the DataProvider
*/
function getPoolDataProvider() external view returns (address);
/**
* @notice Updates the address of the data provider.
* @param newDataProvider The address of the new DataProvider
**/
function setPoolDataProvider(address newDataProvider) external;
}// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
)
external
returns (
uint256 amountA,
uint256 amountB,
uint256 liquidity
);
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
)
external
payable
returns (
uint256 amountToken,
uint256 amountETH,
uint256 liquidity
);
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETHWithPermit(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountToken, uint256 amountETH);
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapETHForExactTokens(
uint256 amountOut,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function quote(
uint256 amountA,
uint256 reserveA,
uint256 reserveB
) external pure returns (uint256 amountB);
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountOut);
function getAmountIn(
uint256 amountOut,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountIn);
function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts);
function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts);
}
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface IUniswapV3Router is IUniswapV3SwapCallback {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
/// @return amountOut The amount of the received token
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { ERC20, SafeTransferLib, Math } from "src/base/ERC4626.sol";
import { Registry } from "src/Registry.sol";
import { Cellar } from "src/base/Cellar.sol";
import { SwapRouter } from "src/modules/swap-router/SwapRouter.sol";
import { PriceRouter } from "src/modules/price-router/PriceRouter.sol";
/**
* @title Base Adaptor
* @notice Base contract all adaptors must inherit from.
* @dev Allows Cellars to interact with arbritrary DeFi assets and protocols.
* @author crispymangoes
*/
abstract contract BaseAdaptor {
using SafeTransferLib for ERC20;
using Math for uint256;
/**
* @notice Attempted to specify an external receiver during a Cellar `callOnAdaptor` call.
*/
error BaseAdaptor__ExternalReceiverBlocked();
/**
* @notice Attempted to deposit to a position where user deposits were not allowed.
*/
error BaseAdaptor__UserDepositsNotAllowed();
/**
* @notice Attempted to withdraw from a position where user withdraws were not allowed.
*/
error BaseAdaptor__UserWithdrawsNotAllowed();
//============================================ Global Functions ===========================================
/**
* @dev Identifier unique to this adaptor for a shared registry.
* Normally the identifier would just be the address of this contract, but this
* Identifier is needed during Cellar Delegate Call Operations, so getting the address
* of the adaptor is more difficult.
*/
function identifier() public pure virtual returns (bytes32) {
return keccak256(abi.encode("Base Adaptor V 0.0"));
}
function SWAP_ROUTER_REGISTRY_SLOT() internal pure returns (uint256) {
return 1;
}
function PRICE_ROUTER_REGISTRY_SLOT() internal pure returns (uint256) {
return 2;
}
//============================================ Implement Base Functions ===========================================
//==================== Base Function Specification ====================
// Base functions are functions designed to help the Cellar interact with
// an adaptor position, strategists are not intended to use these functions.
// Base functions MUST be implemented in adaptor contracts, even if that is just
// adding a revert statement to make them uncallable by normal user operations.
//
// All view Base functions will be called used normal staticcall.
// All mutative Base functions will be called using delegatecall.
//=====================================================================
/**
* @notice Function Cellars call to deposit users funds into holding position.
* @param assets the amount of assets to deposit
* @param adaptorData data needed to deposit into a position
* @param configurationData data settable when strategists add positions to their Cellar
* Allows strategist to control how the adaptor interacts with the position
*/
function deposit(
uint256 assets,
bytes memory adaptorData,
bytes memory configurationData
) public virtual;
/**
* @notice Function Cellars call to withdraw funds from positions to send to users.
* @param receiver the address that should receive withdrawn funds
* @param adaptorData data needed to withdraw from a position
* @param configurationData data settable when strategists add positions to their Cellar
* Allows strategist to control how the adaptor interacts with the position
*/
function withdraw(
uint256 assets,
address receiver,
bytes memory adaptorData,
bytes memory configurationData
) public virtual;
/**
* @notice Function Cellars use to determine `assetOf` balance of an adaptor position.
* @param adaptorData data needed to interact with the position
* @return balance of the position in terms of `assetOf`
*/
function balanceOf(bytes memory adaptorData) public view virtual returns (uint256);
/**
* @notice Functions Cellars use to determine the withdrawable balance from an adaptor position.
* @dev Debt positions MUST return 0 for their `withdrawableFrom`
* @notice accepts adaptorData and configurationData
* @return withdrawable balance of the position in terms of `assetOf`
*/
function withdrawableFrom(bytes memory, bytes memory) public view virtual returns (uint256);
/**
* @notice Function Cellars use to determine the underlying ERC20 asset of a position.
* @param adaptorData data needed to withdraw from a position
* @return the underlying ERC20 asset of a position
*/
function assetOf(bytes memory adaptorData) public view virtual returns (ERC20);
/**
* @notice When positions are added to the Registry, this function can be used in order to figure out
* what assets this adaptor needs to price, and confirm pricing is properly setup.
*/
function assetsUsed(bytes memory adaptorData) public view virtual returns (ERC20[] memory assets) {
assets = new ERC20[](1);
assets[0] = assetOf(adaptorData);
}
/**
* @notice Functions Registry/Cellars use to determine if this adaptor reports debt values.
* @dev returns true if this adaptor reports debt values.
*/
function isDebt() public view virtual returns (bool);
//============================================ Strategist Functions ===========================================
//==================== Strategist Function Specification ====================
// Strategist functions are only callable by strategists through the Cellars
// `callOnAdaptor` function. A cellar will never call any of these functions,
// when a normal user interacts with a cellar(depositing/withdrawing)
//
// All strategist functions will be called using delegatecall.
// Strategist functions are intentionally "blind" to what positions the cellar
// is currently holding. This allows strategists to enter temporary positions
// while rebalancing.
// To mitigate strategist from abusing this and moving funds in untracked
// positions, the cellar will enforce a Total Value Locked check that
// insures TVL has not deviated too much from `callOnAdaptor`.
//===========================================================================
//============================================ Helper Functions ===========================================
/**
* @notice Helper function that allows adaptor calls to use the max available of an ERC20 asset
* by passing in type(uint256).max
* @param token the ERC20 asset to work with
* @param amount when `type(uint256).max` is used, this function returns `token`s `balanceOf`
* otherwise this function returns amount.
*/
function _maxAvailable(ERC20 token, uint256 amount) internal view virtual returns (uint256) {
if (amount == type(uint256).max) return token.balanceOf(address(this));
else return amount;
}
/**
* @notice Helper function that allows adaptors to make swaps using the Swap Router
* @param assetIn the asset to make a swap with
* @param assetOut the asset to get out of the swap
* @param amountIn the amount of `assetIn` to swap with
* @param exchange enum value that determines what exchange to make the swap on
* see SwapRouter.sol
* @param params swap params needed to perform the swap, dependent on which exchange is selected
* see SwapRouter.sol
* @return amountOut the amount of `assetOut` received from the swap.
*/
function swap(
ERC20 assetIn,
ERC20 assetOut,
uint256 amountIn,
SwapRouter.Exchange exchange,
bytes memory params
) public returns (uint256 amountOut) {
// Get the address of the latest swap router.
SwapRouter swapRouter = SwapRouter(Cellar(address(this)).registry().getAddress(SWAP_ROUTER_REGISTRY_SLOT()));
// Approve swap router to swap assets.
assetIn.safeApprove(address(swapRouter), amountIn);
// Perform swap.
amountOut = swapRouter.swap(exchange, params, address(this), assetIn, assetOut);
}
/**
* @notice Helper function that validates external receivers are allowed.
*/
function _externalReceiverCheck(address receiver) internal view {
if (receiver != address(this) && Cellar(address(this)).blockExternalReceiver())
revert BaseAdaptor__ExternalReceiverBlocked();
}
/**
* @notice Attempted oracle swap did not pass slippage check.
*/
error BaseAdaptor__BadSlippage();
/**
* @notice Attempted to make an oracle swap on an unsupported exchange.
*/
error BaseAdaptor__ExchangeNotSupported();
/**
* @notice Helper function to make safe "blind" Uniswap Swaps by comparing value in vs value out of the swap.
* @dev Only works for Uniswap V2 or V3 exchanges.
* @param assetIn the asset to make a swap with
* @param assetOut the asset to get out of the swap
* @param amountIn the amount of `assetIn` to swap with, can be type(uint256).max
* @param exchange enum value that determines what exchange to make the swap on
* see SwapRouter.sol
* @param params swap params needed to perform the swap, dependent on which exchange is selected
* see SwapRouter.sol
* @param slippage number less than 1e18, defining the max swap slippage
* @return amountOut the amount of `assetOut` received from the swap.
*/
function oracleSwap(
ERC20 assetIn,
ERC20 assetOut,
uint256 amountIn,
SwapRouter.Exchange exchange,
bytes memory params,
uint64 slippage
) public returns (uint256 amountOut) {
amountIn = _maxAvailable(assetIn, amountIn);
// Copy over the path/fees then set the amount and minAmount.
if (exchange == SwapRouter.Exchange.UNIV2) {
address[] memory path = abi.decode(params, (address[]));
params = abi.encode(path, amountIn, 0);
} else if (exchange == SwapRouter.Exchange.UNIV3) {
(address[] memory path, uint24[] memory poolFees) = abi.decode(params, (address[], uint24[]));
params = abi.encode(path, poolFees, amountIn, 0);
} else revert BaseAdaptor__ExchangeNotSupported();
// Get the address of the latest swap router.
SwapRouter swapRouter = SwapRouter(Cellar(address(this)).registry().getAddress(SWAP_ROUTER_REGISTRY_SLOT()));
// Get the address of the latest price router.
PriceRouter priceRouter = PriceRouter(
Cellar(address(this)).registry().getAddress(PRICE_ROUTER_REGISTRY_SLOT())
);
// Approve swap router to swap assets.
assetIn.safeApprove(address(swapRouter), amountIn);
// Perform swap.
amountOut = swapRouter.swap(exchange, params, address(this), assetIn, assetOut);
// Make sure amountIn vs amountOut is reasonable.
amountIn = priceRouter.getValue(assetIn, amountIn, assetOut);
uint256 amountInWithSlippage = amountIn.mulDivDown(slippage, 1e18);
if (amountOut < amountInWithSlippage) revert BaseAdaptor__BadSlippage();
}
/**
* @notice Allows strategists to zero out an approval for a given `asset`.
* @param asset the ERC20 asset to revoke `spender`s approval for
* @param spender the address to revoke approval for
*/
function revokeApproval(ERC20 asset, address spender) public {
asset.safeApprove(spender, 0);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { ERC20, SafeTransferLib } from "src/base/ERC4626.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { AutomationCompatibleInterface } from "@chainlink/contracts/src/v0.8/interfaces/AutomationCompatibleInterface.sol";
import { IChainlinkAggregator } from "src/interfaces/external/IChainlinkAggregator.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Math } from "src/utils/Math.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { ICurvePool } from "src/interfaces/external/ICurvePool.sol";
import { IAaveToken } from "src/interfaces/external/IAaveToken.sol";
/**
* @title Sommelier Price Router
* @notice Provides a universal interface allowing Sommelier contracts to retrieve secure pricing
* data from Chainlink.
* @author crispymangoes, Brian Le
*/
contract PriceRouter is Ownable, AutomationCompatibleInterface {
using SafeTransferLib for ERC20;
using SafeCast for int256;
using Math for uint256;
using Address for address;
event AddAsset(address indexed asset);
// =========================================== ASSETS CONFIG ===========================================
/**
* @notice Bare minimum settings all derivatives support.
* @param derivative the derivative used to price the asset
* @param source the address used to price the asset
*/
struct AssetSettings {
uint8 derivative;
address source;
}
/**
* @notice Mapping between an asset to price and its `AssetSettings`.
*/
mapping(ERC20 => AssetSettings) public getAssetSettings;
// ======================================= ADAPTOR OPERATIONS =======================================
/**
* @notice Attempted to set a minimum price below the Chainlink minimum price (with buffer).
* @param minPrice minimum price attempted to set
* @param bufferedMinPrice minimum price that can be set including buffer
*/
error PriceRouter__InvalidMinPrice(uint256 minPrice, uint256 bufferedMinPrice);
/**
* @notice Attempted to set a maximum price above the Chainlink maximum price (with buffer).
* @param maxPrice maximum price attempted to set
* @param bufferedMaxPrice maximum price that can be set including buffer
*/
error PriceRouter__InvalidMaxPrice(uint256 maxPrice, uint256 bufferedMaxPrice);
/**
* @notice Attempted to add an invalid asset.
* @param asset address of the invalid asset
*/
error PriceRouter__InvalidAsset(address asset);
/**
* @notice Attempted to add an asset, but actual answer was outside range of expectedAnswer.
*/
error PriceRouter__BadAnswer(uint256 answer, uint256 expectedAnswer);
/**
* @notice Attempted to perform an operation using an unkown derivative.
*/
error PriceRouter__UnkownDerivative(uint8 unkownDerivative);
/**
* @notice Attempted to add an asset with invalid min/max prices.
* @param min price
* @param max price
*/
error PriceRouter__MinPriceGreaterThanMaxPrice(uint256 min, uint256 max);
/**
* @notice The allowed deviation between the expected answer vs the actual answer.
*/
uint256 public constant EXPECTED_ANSWER_DEVIATION = 0.02e18;
/**
* @notice Stores pricing information during calls.
* @param asset the address of the asset
* @param price the USD price of the asset
* @dev If the price does not fit into a uint96, the asset is NOT added to the cache.
*/
struct PriceCache {
address asset;
uint96 price;
}
/**
* @notice The size of the price cache. A larger cache can hold more values,
* but incurs a larger gas cost overhead. A smaller cache has a
* smaller gas overhead but caches less prices.
*/
uint8 private constant PRICE_CACHE_SIZE = 8;
/**
* @notice Allows owner to add assets to the price router.
* @dev Performs a sanity check by comparing the price router computed price to
* a user input `_expectedAnswer`.
* @param _asset the asset to add to the pricing router
* @param _settings the settings for `_asset`
* @dev The `derivative` value in settings MUST be non zero.
* @param _storage arbitrary bytes data used to configure `_asset` pricing
* @param _expectedAnswer the expected answer for the asset from `_getPriceInUSD`
*/
function addAsset(
ERC20 _asset,
AssetSettings memory _settings,
bytes memory _storage,
uint256 _expectedAnswer
) external onlyOwner {
if (address(_asset) == address(0)) revert PriceRouter__InvalidAsset(address(_asset));
// Zero is an invalid derivative.
if (_settings.derivative == 0) revert PriceRouter__UnkownDerivative(_settings.derivative);
// Call setup function for appropriate derivative.
if (_settings.derivative == 1) {
_setupPriceForChainlinkDerivative(_asset, _settings.source, _storage);
} else if (_settings.derivative == 2) {
_setupPriceForCurveDerivative(_asset, _settings.source, _storage);
} else if (_settings.derivative == 3) {
_setupPriceForCurveV2Derivative(_asset, _settings.source, _storage);
} else if (_settings.derivative == 4) {
_setupPriceForAaveDerivative(_asset, _settings.source, _storage);
} else revert PriceRouter__UnkownDerivative(_settings.derivative);
// Check `_getPriceInUSD` against `_expectedAnswer`.
uint256 minAnswer = _expectedAnswer.mulWadDown((1e18 - EXPECTED_ANSWER_DEVIATION));
uint256 maxAnswer = _expectedAnswer.mulWadDown((1e18 + EXPECTED_ANSWER_DEVIATION));
// Create an empty Price Cache.
PriceCache[PRICE_CACHE_SIZE] memory cache;
getAssetSettings[_asset] = _settings;
uint256 answer = _getPriceInUSD(_asset, _settings, cache);
if (answer < minAnswer || answer > maxAnswer) revert PriceRouter__BadAnswer(answer, _expectedAnswer);
emit AddAsset(address(_asset));
}
/**
* @notice return bool indicating whether or not an asset has been set up.
* @dev Since `addAsset` enforces the derivative is non zero, checking if the stored setting
* is nonzero is sufficient to see if the asset is set up.
*/
function isSupported(ERC20 asset) external view returns (bool) {
return getAssetSettings[asset].derivative > 0;
}
// ======================================= CHAINLINK AUTOMATION =======================================
/**
* @notice `checkUpkeep` is set up to allow for multiple derivatives to use Chainlink Automation.
*/
function checkUpkeep(bytes calldata checkData) external view returns (bool upkeepNeeded, bytes memory performData) {
(uint8 derivative, bytes memory derivativeCheckData) = abi.decode(checkData, (uint8, bytes));
if (derivative == 2) {
(upkeepNeeded, performData) = _checkVirtualPriceBound(derivativeCheckData);
} else if (derivative == 3) {
(upkeepNeeded, performData) = _checkVirtualPriceBound(derivativeCheckData);
} else revert PriceRouter__UnkownDerivative(derivative);
}
/**
* @notice `performUpkeep` is set up to allow for multiple derivatives to use Chainlink Automation.
*/
function performUpkeep(bytes calldata performData) external {
(uint8 derivative, bytes memory derivativePerformData) = abi.decode(performData, (uint8, bytes));
if (derivative == 2) {
_updateVirtualPriceBound(derivativePerformData);
} else if (derivative == 3) {
_updateVirtualPriceBound(derivativePerformData);
} else revert PriceRouter__UnkownDerivative(derivative);
}
// ======================================= PRICING OPERATIONS =======================================
/**
* @notice Get `asset` price in USD.
* @dev Returns price in USD with 8 decimals.
*/
function getPriceInUSD(ERC20 asset) external view returns (uint256) {
AssetSettings memory assetSettings = getAssetSettings[asset];
// Create an empty Price Cache.
PriceCache[PRICE_CACHE_SIZE] memory cache;
return _getPriceInUSD(asset, assetSettings, cache);
}
/**
* @notice Get the value of an asset in terms of another asset.
* @param baseAsset address of the asset to get the price of in terms of the quote asset
* @param amount amount of the base asset to price
* @param quoteAsset address of the asset that the base asset is priced in terms of
* @return value value of the amount of base assets specified in terms of the quote asset
*/
function getValue(
ERC20 baseAsset,
uint256 amount,
ERC20 quoteAsset
) external view returns (uint256 value) {
AssetSettings memory baseSettings = getAssetSettings[baseAsset];
AssetSettings memory quoteSettings = getAssetSettings[quoteAsset];
if (baseSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(baseAsset));
if (quoteSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(quoteAsset));
PriceCache[PRICE_CACHE_SIZE] memory cache;
uint256 priceBaseUSD = _getPriceInUSD(baseAsset, baseSettings, cache);
uint256 priceQuoteUSD = _getPriceInUSD(quoteAsset, quoteSettings, cache);
value = _getValueInQuote(priceBaseUSD, priceQuoteUSD, baseAsset.decimals(), quoteAsset.decimals(), amount);
}
/**
* @notice Helper function that compares `_getValues` between input 0 and input 1.
*/
function getValuesDelta(
ERC20[] calldata baseAssets0,
uint256[] calldata amounts0,
ERC20[] calldata baseAssets1,
uint256[] calldata amounts1,
ERC20 quoteAsset
) external view returns (uint256) {
// Create an empty Price Cache.
PriceCache[PRICE_CACHE_SIZE] memory cache;
uint256 value0 = _getValues(baseAssets0, amounts0, quoteAsset, cache);
uint256 value1 = _getValues(baseAssets1, amounts1, quoteAsset, cache);
return value0 - value1;
}
/**
* @notice Helper function that determines the value of assets using `_getValues`.
*/
function getValues(
ERC20[] calldata baseAssets,
uint256[] calldata amounts,
ERC20 quoteAsset
) external view returns (uint256) {
// Create an empty Price Cache.
PriceCache[PRICE_CACHE_SIZE] memory cache;
return _getValues(baseAssets, amounts, quoteAsset, cache);
}
/**
* @notice Get the exchange rate between two assets.
* @param baseAsset address of the asset to get the exchange rate of in terms of the quote asset
* @param quoteAsset address of the asset that the base asset is exchanged for
* @return exchangeRate rate of exchange between the base asset and the quote asset
*/
function getExchangeRate(ERC20 baseAsset, ERC20 quoteAsset) public view returns (uint256 exchangeRate) {
AssetSettings memory baseSettings = getAssetSettings[baseAsset];
AssetSettings memory quoteSettings = getAssetSettings[quoteAsset];
if (baseSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(baseAsset));
if (quoteSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(quoteAsset));
// Create an empty Price Cache.
PriceCache[PRICE_CACHE_SIZE] memory cache;
// Pass in zero for ethToUsd, since it has not been set yet.
exchangeRate = _getExchangeRate(
baseAsset,
baseSettings,
quoteAsset,
quoteSettings,
quoteAsset.decimals(),
cache
);
}
/**
* @notice Get the exchange rates between multiple assets and another asset.
* @param baseAssets addresses of the assets to get the exchange rates of in terms of the quote asset
* @param quoteAsset address of the asset that the base assets are exchanged for
* @return exchangeRates rate of exchange between the base assets and the quote asset
*/
function getExchangeRates(ERC20[] memory baseAssets, ERC20 quoteAsset)
external
view
returns (uint256[] memory exchangeRates)
{
uint8 quoteAssetDecimals = quoteAsset.decimals();
AssetSettings memory quoteSettings = getAssetSettings[quoteAsset];
if (quoteSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(quoteAsset));
// Create an empty Price Cache.
PriceCache[PRICE_CACHE_SIZE] memory cache;
uint256 numOfAssets = baseAssets.length;
exchangeRates = new uint256[](numOfAssets);
for (uint256 i; i < numOfAssets; i++) {
AssetSettings memory baseSettings = getAssetSettings[baseAssets[i]];
if (baseSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(baseAssets[i]));
exchangeRates[i] = _getExchangeRate(
baseAssets[i],
baseSettings,
quoteAsset,
quoteSettings,
quoteAssetDecimals,
cache
);
}
}
// =========================================== HELPER FUNCTIONS ===========================================
ERC20 private constant WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
/**
* @notice Attempted to update the asset to one that is not supported by the platform.
* @param asset address of the unsupported asset
*/
error PriceRouter__UnsupportedAsset(address asset);
/**
* @notice Gets the exchange rate between a base and a quote asset
* @param baseAsset the asset to convert into quoteAsset
* @param quoteAsset the asset base asset is converted into
* @return exchangeRate value of base asset in terms of quote asset
*/
function _getExchangeRate(
ERC20 baseAsset,
AssetSettings memory baseSettings,
ERC20 quoteAsset,
AssetSettings memory quoteSettings,
uint8 quoteAssetDecimals,
PriceCache[PRICE_CACHE_SIZE] memory cache
) internal view returns (uint256) {
uint256 basePrice = _getPriceInUSD(baseAsset, baseSettings, cache);
uint256 quotePrice = _getPriceInUSD(quoteAsset, quoteSettings, cache);
uint256 exchangeRate = basePrice.mulDivDown(10**quoteAssetDecimals, quotePrice);
return exchangeRate;
}
/**
* @notice Helper function to get an assets price in USD.
* @dev Returns price in USD with 8 decimals.
* @dev Favors using cached prices if available.
*/
function _getPriceInUSD(
ERC20 asset,
AssetSettings memory settings,
PriceCache[PRICE_CACHE_SIZE] memory cache
) internal view returns (uint256) {
// First check if the price is in the price cache.
uint8 lastIndex = PRICE_CACHE_SIZE;
for (uint8 i; i < PRICE_CACHE_SIZE; ++i) {
// Did not find our price in the cache.
if (cache[i].asset == address(0)) {
// Save the last index.
lastIndex = i;
break;
}
// Did find our price in the cache.
if (cache[i].asset == address(asset)) return cache[i].price;
}
// Call get price function using appropriate derivative.
uint256 price;
if (settings.derivative == 1) {
price = _getPriceForChainlinkDerivative(asset, settings.source, cache);
} else if (settings.derivative == 2) {
price = _getPriceForCurveDerivative(asset, settings.source, cache);
} else if (settings.derivative == 3) {
price = _getPriceForCurveV2Derivative(asset, settings.source, cache);
} else if (settings.derivative == 4) {
price = _getPriceForAaveDerivative(asset, settings.source, cache);
} else revert PriceRouter__UnkownDerivative(settings.derivative);
// If there is room in the cache, the price fits in a uint96, then find the next spot available.
if (lastIndex < PRICE_CACHE_SIZE && price <= type(uint96).max) {
for (uint8 i = lastIndex; i < PRICE_CACHE_SIZE; ++i) {
// Found an empty cache slot, so fill it.
if (cache[i].asset == address(0)) {
cache[i] = PriceCache(address(asset), uint96(price));
break;
}
}
}
return price;
}
/**
* @notice math function that preserves precision by multiplying the amountBase before dividing.
* @param priceBaseUSD the base asset price in USD
* @param priceQuoteUSD the quote asset price in USD
* @param baseDecimals the base asset decimals
* @param quoteDecimals the quote asset decimals
* @param amountBase the amount of base asset
*/
function _getValueInQuote(
uint256 priceBaseUSD,
uint256 priceQuoteUSD,
uint8 baseDecimals,
uint8 quoteDecimals,
uint256 amountBase
) internal pure returns (uint256 valueInQuote) {
// Get value in quote asset, but maintain as much precision as possible.
// Cleaner equations below.
// baseToUSD = amountBase * priceBaseUSD / 10**baseDecimals.
// valueInQuote = baseToUSD * 10**quoteDecimals / priceQuoteUSD
valueInQuote = amountBase.mulDivDown((priceBaseUSD * 10**quoteDecimals), (10**baseDecimals * priceQuoteUSD));
}
/**
* @notice Attempted an operation with arrays of unequal lengths that were expected to be equal length.
*/
error PriceRouter__LengthMismatch();
/**
* @notice Get the total value of multiple assets in terms of another asset.
* @param baseAssets addresses of the assets to get the price of in terms of the quote asset
* @param amounts amounts of each base asset to price
* @param quoteAsset address of the assets that the base asset is priced in terms of
* @return value total value of the amounts of each base assets specified in terms of the quote asset
*/
function _getValues(
ERC20[] calldata baseAssets,
uint256[] calldata amounts,
ERC20 quoteAsset,
PriceCache[PRICE_CACHE_SIZE] memory cache
) internal view returns (uint256) {
if (baseAssets.length != amounts.length) revert PriceRouter__LengthMismatch();
uint256 quotePrice;
{
AssetSettings memory quoteSettings = getAssetSettings[quoteAsset];
if (quoteSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(quoteAsset));
quotePrice = _getPriceInUSD(quoteAsset, quoteSettings, cache);
}
uint256 valueInQuote;
// uint256 price;
uint8 quoteDecimals = quoteAsset.decimals();
for (uint8 i = 0; i < baseAssets.length; i++) {
// Skip zero amount values.
if (amounts[i] == 0) continue;
ERC20 baseAsset = baseAssets[i];
if (baseAsset == quoteAsset) valueInQuote += amounts[i];
else {
uint256 basePrice;
{
AssetSettings memory baseSettings = getAssetSettings[baseAsset];
if (baseSettings.derivative == 0) revert PriceRouter__UnsupportedAsset(address(baseAsset));
basePrice = _getPriceInUSD(baseAsset, baseSettings, cache);
}
valueInQuote += _getValueInQuote(
basePrice,
quotePrice,
baseAsset.decimals(),
quoteDecimals,
amounts[i]
);
// uint256 valueInUSD = (amounts[i].mulDivDown(price, 10**baseAsset.decimals()));
// valueInQuote += valueInUSD.mulDivDown(10**quoteDecimals, quotePrice);
}
}
return valueInQuote;
}
// =========================================== CHAINLINK PRICE DERIVATIVE ===========================================\
/**
* @notice Stores data for Chainlink derivative assets.
* @param max the max valid price of the asset
* @param min the min valid price of the asset
* @param heartbeat the max amount of time between price updates
* @param inETH bool indicating whether the price feed is
* denominated in ETH(true) or USD(false)
*/
struct ChainlinkDerivativeStorage {
uint144 max;
uint80 min;
uint24 heartbeat;
bool inETH;
}
/**
* @notice Returns Chainlink Derivative Storage
*/
mapping(ERC20 => ChainlinkDerivativeStorage) public getChainlinkDerivativeStorage;
/**
* @notice If zero is specified for a Chainlink asset heartbeat, this value is used instead.
*/
uint24 public constant DEFAULT_HEART_BEAT = 1 days;
/**
* @notice Setup function for pricing Chainlink derivative assets.
* @dev _source The address of the Chainlink Data feed.
* @dev _storage A ChainlinkDerivativeStorage value defining valid prices.
*/
function _setupPriceForChainlinkDerivative(
ERC20 _asset,
address _source,
bytes memory _storage
) internal {
ChainlinkDerivativeStorage memory parameters = abi.decode(_storage, (ChainlinkDerivativeStorage));
// Use Chainlink to get the min and max of the asset.
IChainlinkAggregator aggregator = IChainlinkAggregator(IChainlinkAggregator(_source).aggregator());
uint256 minFromChainklink = uint256(uint192(aggregator.minAnswer()));
uint256 maxFromChainlink = uint256(uint192(aggregator.maxAnswer()));
// Add a ~10% buffer to minimum and maximum price from Chainlink because Chainlink can stop updating
// its price before/above the min/max price.
uint256 bufferedMinPrice = (minFromChainklink * 1.1e18) / 1e18;
uint256 bufferedMaxPrice = (maxFromChainlink * 0.9e18) / 1e18;
if (parameters.min == 0) {
// Revert if bufferedMinPrice overflows because uint80 is too small to hold the minimum price,
// and lowering it to uint80 is not safe because the price feed can stop being updated before
// it actually gets to that lower price.
if (bufferedMinPrice > type(uint80).max) revert("Buffered Min Overflow");
parameters.min = uint80(bufferedMinPrice);
} else {
if (parameters.min < bufferedMinPrice)
revert PriceRouter__InvalidMinPrice(parameters.min, bufferedMinPrice);
}
if (parameters.max == 0) {
//Do not revert even if bufferedMaxPrice is greater than uint144, because lowering it to uint144 max is more conservative.
parameters.max = bufferedMaxPrice > type(uint144).max ? type(uint144).max : uint144(bufferedMaxPrice);
} else {
if (parameters.max > bufferedMaxPrice)
revert PriceRouter__InvalidMaxPrice(parameters.max, bufferedMaxPrice);
}
if (parameters.min >= parameters.max)
revert PriceRouter__MinPriceGreaterThanMaxPrice(parameters.min, parameters.max);
parameters.heartbeat = parameters.heartbeat != 0 ? parameters.heartbeat : DEFAULT_HEART_BEAT;
getChainlinkDerivativeStorage[_asset] = parameters;
}
/**
* @notice Get the price of a Chainlink derivative in terms of USD.
*/
function _getPriceForChainlinkDerivative(
ERC20 _asset,
address _source,
PriceCache[PRICE_CACHE_SIZE] memory cache
) internal view returns (uint256) {
ChainlinkDerivativeStorage memory parameters = getChainlinkDerivativeStorage[_asset];
IChainlinkAggregator aggregator = IChainlinkAggregator(_source);
(, int256 _price, , uint256 _timestamp, ) = aggregator.latestRoundData();
uint256 price = _price.toUint256();
_checkPriceFeed(address(_asset), price, _timestamp, parameters.max, parameters.min, parameters.heartbeat);
// If price is in ETH, then convert price into USD.
if (parameters.inETH) {
uint256 _ethToUsd = _getPriceInUSD(WETH, getAssetSettings[WETH], cache);
price = price.mulWadDown(_ethToUsd);
}
return price;
}
/**
* @notice Attempted an operation to price an asset that under its minimum valid price.
* @param asset address of the asset that is under its minimum valid price
* @param price price of the asset
* @param minPrice minimum valid price of the asset
*/
error PriceRouter__AssetBelowMinPrice(address asset, uint256 price, uint256 minPrice);
/**
* @notice Attempted an operation to price an asset that under its maximum valid price.
* @param asset address of the asset that is under its maximum valid price
* @param price price of the asset
* @param maxPrice maximum valid price of the asset
*/
error PriceRouter__AssetAboveMaxPrice(address asset, uint256 price, uint256 maxPrice);
/**
* @notice Attempted to fetch a price for an asset that has not been updated in too long.
* @param asset address of the asset thats price is stale
* @param timeSinceLastUpdate seconds since the last price update
* @param heartbeat maximum allowed time between price updates
*/
error PriceRouter__StalePrice(address asset, uint256 timeSinceLastUpdate, uint256 heartbeat);
/**
* @notice helper function to validate a price feed is safe to use.
* @param asset ERC20 asset price feed data is for.
* @param value the price value the price feed gave.
* @param timestamp the last timestamp the price feed was updated.
* @param max the upper price bound
* @param min the lower price bound
* @param heartbeat the max amount of time between price updates
*/
function _checkPriceFeed(
address asset,
uint256 value,
uint256 timestamp,
uint144 max,
uint88 min,
uint24 heartbeat
) internal view {
if (value < min) revert PriceRouter__AssetBelowMinPrice(address(asset), value, min);
if (value > max) revert PriceRouter__AssetAboveMaxPrice(address(asset), value, max);
uint256 timeSinceLastUpdate = block.timestamp - timestamp;
if (timeSinceLastUpdate > heartbeat)
revert PriceRouter__StalePrice(address(asset), timeSinceLastUpdate, heartbeat);
}
// ======================================== CURVE VIRTUAL PRICE BOUND ========================================
/**
* @notice Curve virtual price is susceptible to re-entrancy attacks, if the attacker adds/removes pool liquidity,
* and re-enters into one of our contracts. To mitigate this, all curve pricing operations check
* the current `pool.get_virtual_price()` against logical bounds.
* @notice These logical bounds are updated when `addAsset` is called, or Chainlink Automation detects that
* the bounds need to be updated, and that the gas price is reasonable.
* @notice Once the on chain virtual price goes out of bounds, all pricing operations will revert for that Curve LP,
* which means any Cellars using that Curve LP are effectively frozen until the virtual price bounds are updated
* by Chainlink. If this is not happening in a timely manner( IE network is abnormally busy), the owner of this
* contract can raise the `gasConstant` to a value that better reflects the floor gas price of the network.
* Which will cause Chainlink nodes to update virtual price bounds faster.
*/
/**
* @param datum the virtual price to base posDelta and negDelta off of, 8 decimals
* @param timeLastUpdated the timestamp this datum was updated
* @param posDelta multipler >= 1e8 defining the logical upper bound for this virtual price, 8 decimals
* @param negDelta multipler <= 1e8 defining the logical lower bound for this virtual price, 8 decimals
* @param rateLimit the minimum amount of time that must pass between updates
* @dev Curve virtual price values should update slowly, hence why this contract enforces a rate limit.
* @dev During datum updates, the max/min new datum corresponds to the current upper/lower bound.
*/
struct VirtualPriceBound {
uint96 datum;
uint64 timeLastUpdated;
uint32 posDelta;
uint32 negDelta;
uint32 rateLimit;
}
/**
* @notice Returns a Curve asset virtual price bound
*/
mapping(address => VirtualPriceBound) public getVirtualPriceBound;
/**
* @dev If ZERO is specified for an assets `rateLimit` this value is used instead.
*/
uint32 public constant DEFAULT_RATE_LIMIT = 1 days;
/**
* @notice Chainlink Fast Gas Feed for ETH Mainnet.
*/
address public ETH_FAST_GAS_FEED = 0x169E633A2D1E6c10dD91238Ba11c4A708dfEF37C;
/**
* @notice Allows owner to set a new gas feed.
* @notice Can be set to zero address to skip gas check.
*/
function setGasFeed(address gasFeed) external onlyOwner {
ETH_FAST_GAS_FEED = gasFeed;
}
/**
* @notice Dictates how aggressive keepers are with updating Curve pool virtual price values.
* @dev A larger `gasConstant` will raise the `gasPriceLimit`, while a smaller `gasConstant`
* will lower the `gasPriceLimit`.
*/
uint256 public gasConstant = 200e9;
/**
* @notice Allows owner to set a new gas constant.
*/
function setGasConstant(uint256 newConstant) external onlyOwner {
gasConstant = newConstant;
}
/**
* @notice Dictates the minimum delta required for an upkeep.
* @dev If the max delta found is less than this, then checkUpkeep returns false.
*/
uint256 public minDelta = 0.05e18;
/**
* @notice Allows owner to set a new minimum delta.
*/
function setMinDelta(uint256 newMinDelta) external onlyOwner {
minDelta = newMinDelta;
}
/**
* @notice Stores all Curve Assets this contract prices, so Automation can loop through it.
*/
address[] public curveAssets;
/**
* @notice Allows owner to update a Curve asset's virtual price parameters..
*/
function updateVirtualPriceBound(
address _asset,
uint32 _posDelta,
uint32 _negDelta,
uint32 _rateLimit
) external onlyOwner {
VirtualPriceBound storage vpBound = getVirtualPriceBound[_asset];
vpBound.posDelta = _posDelta;
vpBound.negDelta = _negDelta;
vpBound.rateLimit = _rateLimit == 0 ? DEFAULT_RATE_LIMIT : _rateLimit;
}
/**
* @notice Logic ran by Chainlink Automation to determine if virtual price bounds need to be updated.
* @dev `checkData` should be a start and end value indicating where to start and end in the `curveAssets` array.
* @dev The end index can be zero, or greater than the current length of `curveAssets`.
* Doing this makes end = curveAssets.length.
* @dev `performData` is the target index in `curveAssets` that needs its bounds updated.
*/
function _checkVirtualPriceBound(bytes memory checkData)
internal
view
returns (bool upkeepNeeded, bytes memory performData)
{
// Decode checkData to get start and end index.
(uint256 start, uint256 end) = abi.decode(checkData, (uint256, uint256));
if (end == 0 || end > curveAssets.length) end = curveAssets.length;
// Loop through all curve assets, and find the asset with the largest delta(the one that needs to be updated the most).
uint256 maxDelta;
uint256 targetIndex;
for (uint256 i = start; i < end; i++) {
address asset = curveAssets[i];
VirtualPriceBound memory vpBound = getVirtualPriceBound[asset];
// Check to see if this virtual price was updated recently.
if ((block.timestamp - vpBound.timeLastUpdated) < vpBound.rateLimit) continue;
// Check current virtual price against upper and lower bounds to find the delta.
uint256 currentVirtualPrice = ICurvePool(getAssetSettings[ERC20(asset)].source).get_virtual_price();
currentVirtualPrice = currentVirtualPrice.changeDecimals(18, 8);
uint256 delta;
if (currentVirtualPrice > vpBound.datum) {
uint256 upper = uint256(vpBound.datum).mulDivDown(vpBound.posDelta, 1e8);
uint256 ceiling = upper - vpBound.datum;
uint256 current = currentVirtualPrice - vpBound.datum;
delta = _getDelta(ceiling, current);
} else {
uint256 lower = uint256(vpBound.datum).mulDivDown(vpBound.negDelta, 1e8);
uint256 ceiling = vpBound.datum - lower;
uint256 current = vpBound.datum - currentVirtualPrice;
delta = _getDelta(ceiling, current);
}
// Save the largest delta for the upkeep.
if (delta > maxDelta) {
maxDelta = delta;
targetIndex = i;
}
}
// If the largest delta must be greater/equal to `minDelta` to continue.
if (maxDelta >= minDelta) {
// If gas feed is not set, skip the gas check.
if (ETH_FAST_GAS_FEED == address(0)) {
// No Gas Check needed.
upkeepNeeded = true;
performData = abi.encode(targetIndex);
} else {
// Run a gas check to determine if it makes sense to update the target curve asset.
uint256 gasPriceLimit = gasConstant.mulDivDown(maxDelta**3, 1e54); // 54 comes from 18 * 3.
uint256 currentGasPrice = uint256(IChainlinkAggregator(ETH_FAST_GAS_FEED).latestAnswer());
if (currentGasPrice <= gasPriceLimit) {
upkeepNeeded = true;
performData = abi.encode(targetIndex);
}
}
}
}
/**
* @notice Attempted to call a function only the Chainlink Registry can call.
*/
error PriceRouter__OnlyAutomationRegistry();
/**
* @notice Attempted to update a virtual price too soon.
*/
error PriceRouter__VirtualPriceRateLimiter();
/**
* @notice Attempted to update a virtual price bound that did not need to be updated.
*/
error PriceRouter__NothingToUpdate();
/**
* @notice Chainlink's Automation Registry contract address.
*/
address public automationRegistry = 0x02777053d6764996e594c3E88AF1D58D5363a2e6;
/**
* @notice Allows owner to update the Automation Registry.
* @dev In rare cases, Chainlink's registry CAN change.
*/
function setAutomationRegistry(address newRegistry) external onlyOwner {
automationRegistry = newRegistry;
}
/**
* @notice Curve virtual price is susceptible to re-entrancy attacks, if the attacker adds/removes pool liquidity.
* To stop this we check the virtual price against logical bounds.
* @dev Only the chainlink registry can call this function, so we know that Chainlink nodes will not be
* re-entering into the Curve pool, so it is safe to use the current on chain virtual price.
* @notice Updating the virtual price is rate limited by `VirtualPriceBound.raetLimit` and can only be
* updated at most to the lower or upper bound of the current datum.
* This is intentional since curve pool price should not be volatile, and if they are, then
* we WANT that Curve LP pools TX pricing to revert.
*/
function _updateVirtualPriceBound(bytes memory performData) internal {
// Make sure only the Automation Registry can call this function.
if (msg.sender != automationRegistry) revert PriceRouter__OnlyAutomationRegistry();
// Grab the target index from performData.
uint256 index = abi.decode(performData, (uint256));
address asset = curveAssets[index];
VirtualPriceBound storage vpBound = getVirtualPriceBound[asset];
// Enfore rate limit check.
if ((block.timestamp - vpBound.timeLastUpdated) < vpBound.rateLimit)
revert PriceRouter__VirtualPriceRateLimiter();
// Determine what the new Datum should be.
uint256 currentVirtualPrice = ICurvePool(getAssetSettings[ERC20(asset)].source).get_virtual_price();
currentVirtualPrice = currentVirtualPrice.changeDecimals(18, 8);
if (currentVirtualPrice > vpBound.datum) {
uint256 upper = uint256(vpBound.datum).mulDivDown(vpBound.posDelta, 1e8);
vpBound.datum = uint96(currentVirtualPrice > upper ? upper : currentVirtualPrice);
} else if (currentVirtualPrice < vpBound.datum) {
uint256 lower = uint256(vpBound.datum).mulDivDown(vpBound.negDelta, 1e8);
vpBound.datum = uint96(currentVirtualPrice < lower ? lower : currentVirtualPrice);
} else {
revert PriceRouter__NothingToUpdate();
}
// Update the stored timestamp.
vpBound.timeLastUpdated = uint64(block.timestamp);
}
/**
* @notice Returns a percent delta representing where `current` is in reference to `ceiling`.
* Example, if current == 0, this would return a 0.
* if current == ceiling, this would return a 1e18.
* if current == (ceiling) / 2, this would return 0.5e18.
*/
function _getDelta(uint256 ceiling, uint256 current) internal pure returns (uint256) {
return current.mulDivDown(1e18, ceiling);
}
/**
* @notice Attempted to price a curve asset that was below its logical minimum price.
*/
error PriceRouter__CurrentBelowLowerBound(uint256 current, uint256 lower);
/**
* @notice Attempted to price a curve asset that was above its logical maximum price.
*/
error PriceRouter__CurrentAboveUpperBound(uint256 current, uint256 upper);
/**
* @notice Enforces a logical price bound on Curve pool tokens.
*/
function _checkBounds(
uint256 lower,
uint256 upper,
uint256 current
) internal pure {
if (current < lower) revert PriceRouter__CurrentBelowLowerBound(current, lower);
if (current > upper) revert PriceRouter__CurrentAboveUpperBound(current, upper);
}
// =========================================== CURVE PRICE DERIVATIVE ===========================================
/**
* @notice Curve Derivative Storage
* @dev Stores an array of the underlying token addresses in the curve pool.
*/
mapping(ERC20 => address[]) public getCurveDerivativeStorage;
/**
* @notice Setup function for pricing Curve derivative assets.
* @dev _source The address of the Curve Pool.
* @dev _storage A VirtualPriceBound value for this asset.
* @dev Assumes that curve pools never add or remove tokens.
*/
function _setupPriceForCurveDerivative(
ERC20 _asset,
address _source,
bytes memory _storage
) internal {
ICurvePool pool = ICurvePool(_source);
uint8 coinsLength = 0;
// Figure out how many tokens are in the curve pool.
while (true) {
try pool.coins(coinsLength) {
coinsLength++;
} catch {
break;
}
}
// Save the pools tokens to reduce gas for pricing calls.
address[] memory coins = new address[](coinsLength);
for (uint256 i = 0; i < coinsLength; i++) {
coins[i] = pool.coins(i);
}
getCurveDerivativeStorage[_asset] = coins;
curveAssets.push(address(_asset));
// Setup virtual price bound.
VirtualPriceBound memory vpBound = abi.decode(_storage, (VirtualPriceBound));
uint256 upper = uint256(vpBound.datum).mulDivDown(vpBound.posDelta, 1e8);
upper = upper.changeDecimals(8, 18);
uint256 lower = uint256(vpBound.datum).mulDivDown(vpBound.negDelta, 1e8);
lower = lower.changeDecimals(8, 18);
_checkBounds(lower, upper, pool.get_virtual_price());
if (vpBound.rateLimit == 0) vpBound.rateLimit = DEFAULT_RATE_LIMIT;
vpBound.timeLastUpdated = uint64(block.timestamp);
getVirtualPriceBound[address(_asset)] = vpBound;
}
/**
* @notice Get the price of a CurveV1 derivative in terms of USD.
*/
function _getPriceForCurveDerivative(
ERC20 asset,
address _source,
PriceCache[PRICE_CACHE_SIZE] memory cache
) internal view returns (uint256 price) {
ICurvePool pool = ICurvePool(_source);
address[] memory coins = getCurveDerivativeStorage[asset];
uint256 minPrice = type(uint256).max;
for (uint256 i = 0; i < coins.length; i++) {
ERC20 poolAsset = ERC20(coins[i]);
uint256 tokenPrice = _getPriceInUSD(poolAsset, getAssetSettings[poolAsset], cache);
if (tokenPrice < minPrice) minPrice = tokenPrice;
}
if (minPrice == type(uint256).max) revert("Min price not found.");
// Check that virtual price is within bounds.
uint256 virtualPrice = pool.get_virtual_price();
VirtualPriceBound memory vpBound = getVirtualPriceBound[address(asset)];
uint256 upper = uint256(vpBound.datum).mulDivDown(vpBound.posDelta, 1e8);
upper = upper.changeDecimals(8, 18);
uint256 lower = uint256(vpBound.datum).mulDivDown(vpBound.negDelta, 1e8);
lower = lower.changeDecimals(8, 18);
_checkBounds(lower, upper, virtualPrice);
// Virtual price is based off the Curve Token decimals.
uint256 curveTokenDecimals = ERC20(asset).decimals();
price = minPrice.mulDivDown(virtualPrice, 10**curveTokenDecimals);
}
// =========================================== CURVEV2 PRICE DERIVATIVE ===========================================
/**
* @notice Setup function for pricing CurveV2 derivative assets.
* @dev _source The address of the CurveV2 Pool.
* @dev _storage A VirtualPriceBound value for this asset.
* @dev Assumes that curve pools never add or remove tokens.
*/
function _setupPriceForCurveV2Derivative(
ERC20 _asset,
address _source,
bytes memory _storage
) internal {
ICurvePool pool = ICurvePool(_source);
uint8 coinsLength = 0;
// Figure out how many tokens are in the curve pool.
while (true) {
try pool.coins(coinsLength) {
coinsLength++;
} catch {
break;
}
}
address[] memory coins = new address[](coinsLength);
for (uint256 i = 0; i < coinsLength; i++) {
coins[i] = pool.coins(i);
}
getCurveDerivativeStorage[_asset] = coins;
curveAssets.push(address(_asset));
// Setup virtual price bound.
VirtualPriceBound memory vpBound = abi.decode(_storage, (VirtualPriceBound));
uint256 upper = uint256(vpBound.datum).mulDivDown(vpBound.posDelta, 1e8);
upper = upper.changeDecimals(8, 18);
uint256 lower = uint256(vpBound.datum).mulDivDown(vpBound.negDelta, 1e8);
lower = lower.changeDecimals(8, 18);
_checkBounds(lower, upper, pool.get_virtual_price());
if (vpBound.rateLimit == 0) vpBound.rateLimit = DEFAULT_RATE_LIMIT;
vpBound.timeLastUpdated = uint64(block.timestamp);
getVirtualPriceBound[address(_asset)] = vpBound;
}
uint256 private constant GAMMA0 = 28000000000000;
uint256 private constant A0 = 2 * 3**3 * 10000;
uint256 private constant DISCOUNT0 = 1087460000000000;
// x has 36 decimals
// result has 18 decimals.
function _cubicRoot(uint256 x) internal pure returns (uint256) {
uint256 D = x / 1e18;
for (uint8 i; i < 256; i++) {
uint256 diff;
uint256 D_prev = D;
D = (D * (2 * 1e18 + ((((x / D) * 1e18) / D) * 1e18) / D)) / (3 * 1e18);
if (D > D_prev) diff = D - D_prev;
else diff = D_prev - D;
if (diff <= 1 || diff * 10**18 < D) return D;
}
revert("Did not converge");
}
/**
* Inspired by https://etherscan.io/address/0xE8b2989276E2Ca8FDEA2268E3551b2b4B2418950#code
* @notice Get the price of a CurveV1 derivative in terms of USD.
*/
function _getPriceForCurveV2Derivative(
ERC20 asset,
address _source,
PriceCache[PRICE_CACHE_SIZE] memory cache
) internal view returns (uint256) {
ICurvePool pool = ICurvePool(_source);
// Check that virtual price is within bounds.
uint256 virtualPrice = pool.get_virtual_price();
VirtualPriceBound memory vpBound = getVirtualPriceBound[address(asset)];
uint256 upper = uint256(vpBound.datum).mulDivDown(vpBound.posDelta, 1e8);
upper = upper.changeDecimals(8, 18);
uint256 lower = uint256(vpBound.datum).mulDivDown(vpBound.negDelta, 1e8);
lower = lower.changeDecimals(8, 18);
_checkBounds(lower, upper, virtualPrice);
address[] memory coins = getCurveDerivativeStorage[asset];
ERC20 token0 = ERC20(coins[0]);
if (coins.length == 2) {
return pool.lp_price().mulDivDown(_getPriceInUSD(token0, getAssetSettings[token0], cache), 1e18);
} else if (coins.length == 3) {
uint256 t1Price = pool.price_oracle(0);
uint256 t2Price = pool.price_oracle(1);
uint256 maxPrice = (3 * virtualPrice * _cubicRoot(t1Price * t2Price)) / 1e18;
{
uint256 g = pool.gamma().mulDivDown(1e18, GAMMA0);
uint256 a = pool.A().mulDivDown(1e18, A0);
uint256 coefficient = (g**2 / 1e18) * a;
uint256 discount = coefficient > 1e34 ? coefficient : 1e34;
discount = _cubicRoot(discount).mulDivDown(DISCOUNT0, 1e18);
maxPrice -= maxPrice.mulDivDown(discount, 1e18);
}
return maxPrice.mulDivDown(_getPriceInUSD(token0, getAssetSettings[token0], cache), 1e18);
} else revert("Unsupported Pool");
}
// =========================================== AAVE PRICE DERIVATIVE ===========================================
/**
* @notice Aave Derivative Storage
*/
mapping(ERC20 => ERC20) public getAaveDerivativeStorage;
/**
* @notice Setup function for pricing Aave derivative assets.
* @dev _source The address of the aToken.
* @dev _storage is not used.
*/
function _setupPriceForAaveDerivative(
ERC20 _asset,
address _source,
bytes memory
) internal {
IAaveToken aToken = IAaveToken(_source);
getAaveDerivativeStorage[_asset] = ERC20(aToken.UNDERLYING_ASSET_ADDRESS());
}
/**
* @notice Get the price of an Aave derivative in terms of USD.
*/
function _getPriceForAaveDerivative(
ERC20 asset,
address,
PriceCache[PRICE_CACHE_SIZE] memory cache
) internal view returns (uint256) {
asset = getAaveDerivativeStorage[asset];
return _getPriceInUSD(asset, getAssetSettings[asset], cache);
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { ERC20, SafeTransferLib } from "src/base/ERC4626.sol";
import { Multicall } from "src/base/Multicall.sol";
import { IUniswapV2Router02 as IUniswapV2Router } from "src/interfaces/external/IUniswapV2Router02.sol";
import { IUniswapV3Router } from "src/interfaces/external/IUniswapV3Router.sol";
/**
* @title Sommelier Swap Router
* @notice Provides a universal interface allowing Sommelier contracts to interact with multiple
* different exchanges to perform swaps.
* @dev Perform multiple swaps using Multicall.
* @author crispymangoes, Brian Le
*/
contract SwapRouter is Multicall {
using SafeTransferLib for ERC20;
/**
* @param UNIV2 Uniswap V2
* @param UNIV3 Uniswap V3
*/
enum Exchange {
UNIV2,
UNIV3
}
/**
* @notice Get the selector of the function to call in order to perform swap with a given exchange.
*/
mapping(Exchange => bytes4) public getExchangeSelector;
// ========================================== CONSTRUCTOR ==========================================
/**
* @notice Uniswap V2 swap router contract.
*/
IUniswapV2Router public immutable uniswapV2Router; // 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
/**
* @notice Uniswap V3 swap router contract.
*/
IUniswapV3Router public immutable uniswapV3Router; // 0xE592427A0AEce92De3Edee1F18E0157C05861564
/**
* @param _uniswapV2Router address of the Uniswap V2 swap router contract
* @param _uniswapV3Router address of the Uniswap V3 swap router contract
*/
constructor(IUniswapV2Router _uniswapV2Router, IUniswapV3Router _uniswapV3Router) {
// Set up all exchanges.
uniswapV2Router = _uniswapV2Router;
uniswapV3Router = _uniswapV3Router;
// Set up mapping between IDs and selectors.
getExchangeSelector[Exchange.UNIV2] = SwapRouter(this).swapWithUniV2.selector;
getExchangeSelector[Exchange.UNIV3] = SwapRouter(this).swapWithUniV3.selector;
}
// ======================================= SWAP OPERATIONS =======================================
/**
* @notice Attempted to perform a swap that reverted without a message.
*/
error SwapRouter__SwapReverted();
/**
* @notice Attempted to perform a swap with mismatched assetIn and swap data.
* @param actual the address encoded into the swap data
* @param expected the address passed in with assetIn
*/
error SwapRouter__AssetInMisMatch(address actual, address expected);
/**
* @notice Attempted to perform a swap with mismatched assetOut and swap data.
* @param actual the address encoded into the swap data
* @param expected the address passed in with assetIn
*/
error SwapRouter__AssetOutMisMatch(address actual, address expected);
/**
* @notice Perform a swap using a supported exchange.
* @param exchange value dictating which exchange to use to make the swap
* @param swapData encoded data used for the swap
* @param receiver address to send the received assets to
* @return amountOut amount of assets received from the swap
*/
function swap(
Exchange exchange,
bytes memory swapData,
address receiver,
ERC20 assetIn,
ERC20 assetOut
) external returns (uint256 amountOut) {
// Route swap call to appropriate function using selector.
(bool success, bytes memory result) = address(this).delegatecall(
abi.encodeWithSelector(getExchangeSelector[exchange], swapData, receiver, assetIn, assetOut)
);
if (!success) {
// If there is return data, the call reverted with a reason or a custom error so we
// bubble up the error message.
if (result.length > 0) {
assembly {
let returndata_size := mload(result)
revert(add(32, result), returndata_size)
}
} else {
revert SwapRouter__SwapReverted();
}
}
amountOut = abi.decode(result, (uint256));
}
/**
* @notice Perform a swap using Uniswap V2.
* @param swapData bytes variable storing the following swap information:
* address[] path: array of addresses dictating what swap path to follow
* uint256 amount: amount of the first asset in the path to swap
* uint256 amountOutMin: the minimum amount of the last asset in the path to receive
* @param receiver address to send the received assets to
* @return amountOut amount of assets received from the swap
*/
function swapWithUniV2(
bytes memory swapData,
address receiver,
ERC20 assetIn,
ERC20 assetOut
) public returns (uint256 amountOut) {
(address[] memory path, uint256 amount, uint256 amountOutMin) = abi.decode(
swapData,
(address[], uint256, uint256)
);
// Check that path matches assetIn and assetOut.
if (assetIn != ERC20(path[0])) revert SwapRouter__AssetInMisMatch(path[0], address(assetIn));
if (assetOut != ERC20(path[path.length - 1]))
revert SwapRouter__AssetOutMisMatch(path[path.length - 1], address(assetOut));
// Transfer assets to this contract to swap.
assetIn.safeTransferFrom(msg.sender, address(this), amount);
// Approve assets to be swapped through the router.
assetIn.safeApprove(address(uniswapV2Router), amount);
// Execute the swap.
uint256[] memory amountsOut = uniswapV2Router.swapExactTokensForTokens(
amount,
amountOutMin,
path,
receiver,
block.timestamp + 60
);
amountOut = amountsOut[amountsOut.length - 1];
_checkApprovalIsZero(assetIn, address(uniswapV2Router));
}
/**
* @notice Perform a swap using Uniswap V3.
* @param swapData bytes variable storing the following swap information
* address[] path: array of addresses dictating what swap path to follow
* uint24[] poolFees: array of pool fees dictating what swap pools to use
* uint256 amount: amount of the first asset in the path to swap
* uint256 amountOutMin: the minimum amount of the last asset in the path to receive
* @param receiver address to send the received assets to
* @return amountOut amount of assets received from the swap
*/
function swapWithUniV3(
bytes memory swapData,
address receiver,
ERC20 assetIn,
ERC20 assetOut
) public returns (uint256 amountOut) {
(address[] memory path, uint24[] memory poolFees, uint256 amount, uint256 amountOutMin) = abi.decode(
swapData,
(address[], uint24[], uint256, uint256)
);
// Check that path matches assetIn and assetOut.
if (assetIn != ERC20(path[0])) revert SwapRouter__AssetInMisMatch(path[0], address(assetIn));
if (assetOut != ERC20(path[path.length - 1]))
revert SwapRouter__AssetOutMisMatch(path[path.length - 1], address(assetOut));
// Transfer assets to this contract to swap.
assetIn.safeTransferFrom(msg.sender, address(this), amount);
// Approve assets to be swapped through the router.
assetIn.safeApprove(address(uniswapV3Router), amount);
// Encode swap parameters.
bytes memory encodePackedPath = abi.encodePacked(address(assetIn));
for (uint256 i = 1; i < path.length; i++)
encodePackedPath = abi.encodePacked(encodePackedPath, poolFees[i - 1], path[i]);
// Execute the swap.
amountOut = uniswapV3Router.exactInput(
IUniswapV3Router.ExactInputParams({
path: encodePackedPath,
recipient: receiver,
deadline: block.timestamp + 60,
amountIn: amount,
amountOutMinimum: amountOutMin
})
);
_checkApprovalIsZero(assetIn, address(uniswapV3Router));
}
// ======================================= HELPER FUNCTIONS =======================================
/**
* @notice Emitted when a swap does not use all the assets swap router approved.
*/
error SwapRouter__UnusedApproval();
/**
* @notice Helper function that reverts if the Swap Router has unused approval after a swap is made.
*/
function _checkApprovalIsZero(ERC20 asset, address spender) internal view {
if (asset.allowance(address(this), spender) != 0) revert SwapRouter__UnusedApproval();
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
library Math {
/**
* @notice Substract with a floor of 0 for the result.
*/
function subMinZero(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? x - y : 0;
}
/**
* @notice Used to change the decimals of precision used for an amount.
*/
function changeDecimals(
uint256 amount,
uint8 fromDecimals,
uint8 toDecimals
) internal pure returns (uint256) {
if (fromDecimals == toDecimals) {
return amount;
} else if (fromDecimals < toDecimals) {
return amount * 10**(toDecimals - fromDecimals);
} else {
return amount / 10**(fromDecimals - toDecimals);
}
}
// ===================================== OPENZEPPELIN'S MATH =====================================
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
// ================================= SOLMATE's FIXEDPOINTMATHLIB =================================
uint256 public 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 mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// Divide z by the denominator.
z := div(z, denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}
}// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
/**
* @notice A library to extend the uint32 array data type.
*/
library Uint32Array {
// =========================================== ADDRESS STORAGE ===========================================
/**
* @notice Add an uint32 to the array at a given index.
* @param array uint32 array to add the uint32 to
* @param index index to add the uint32 at
* @param value uint32 to add to the array
*/
function add(
uint32[] storage array,
uint32 index,
uint32 value
) internal {
uint256 len = array.length;
if (len > 0) {
array.push(array[len - 1]);
for (uint256 i = len - 1; i > index; i--) array[i] = array[i - 1];
array[index] = value;
} else {
array.push(value);
}
}
/**
* @notice Remove a uint32 from the array at a given index.
* @param array uint32 array to remove the uint32 from
* @param index index to remove the uint32 at
*/
function remove(uint32[] storage array, uint32 index) internal {
uint256 len = array.length;
require(index < len, "Index out of bounds");
for (uint256 i = index; i < len - 1; i++) array[i] = array[i + 1];
array.pop();
}
/**
* @notice Check whether an array contains an uint32.
* @param array uint32 array to check
* @param value uint32 to check for
*/
function contains(uint32[] storage array, uint32 value) internal view returns (bool) {
for (uint256 i; i < array.length; i++) if (value == array[i]) return true;
return false;
}
}{
"remappings": [
"@chainlink/=lib/chainlink/",
"@compound/=lib/compound-protocol/contracts/",
"@ds-test/=lib/forge-std/lib/ds-test/src/",
"@ensdomains/=node_modules/@ensdomains/",
"@forge-std/=lib/forge-std/src/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"@solmate/=lib/solmate/src/",
"@uniswap/v3-core/=lib/v3-core/",
"@uniswap/v3-periphery/=lib/v3-periphery/",
"@uniswapV3C/=lib/v3-core.git/contracts/",
"@uniswapV3P/=lib/v3-periphery.git/contracts/",
"chainlink/=lib/chainlink/contracts/src/v0.8/dev/vendor/@arbitrum/nitro-contracts/src/",
"compound-protocol/=lib/compound-protocol/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"eth-gas-reporter/=node_modules/eth-gas-reporter/",
"forge-std/=lib/forge-std/src/",
"hardhat/=node_modules/hardhat/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"solmate/=lib/solmate/src/",
"v3-core.git/=lib/v3-core.git/contracts/",
"v3-periphery.git/=lib/v3-periphery.git/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "london",
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"untrackedDebtPosition","type":"address"}],"name":"AaveDebtTokenAdaptor__DebtPositionsMustBeTracked","type":"error"},{"inputs":[],"name":"AaveDebtTokenAdaptor__HealthFactorTooLow","type":"error"},{"inputs":[],"name":"BaseAdaptor__BadSlippage","type":"error"},{"inputs":[],"name":"BaseAdaptor__ExchangeNotSupported","type":"error"},{"inputs":[],"name":"BaseAdaptor__ExternalReceiverBlocked","type":"error"},{"inputs":[],"name":"BaseAdaptor__UserDepositsNotAllowed","type":"error"},{"inputs":[],"name":"BaseAdaptor__UserWithdrawsNotAllowed","type":"error"},{"inputs":[{"internalType":"bytes","name":"adaptorData","type":"bytes"}],"name":"assetOf","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"adaptorData","type":"bytes"}],"name":"assetsUsed","outputs":[{"internalType":"contract ERC20[]","name":"assets","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"adaptorData","type":"bytes"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"debtTokenToBorrow","type":"address"},{"internalType":"uint256","name":"amountToBorrow","type":"uint256"}],"name":"borrowFromAave","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"loanToken","type":"address[]"},{"internalType":"uint256[]","name":"loanAmount","type":"uint256[]"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"flashLoan","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"identifier","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"isDebt","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"assetIn","type":"address"},{"internalType":"contract ERC20","name":"assetOut","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"enum SwapRouter.Exchange","name":"exchange","type":"uint8"},{"internalType":"bytes","name":"params","type":"bytes"},{"internalType":"uint64","name":"slippage","type":"uint64"}],"name":"oracleSwap","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"tokenToRepay","type":"address"},{"internalType":"uint256","name":"amountToRepay","type":"uint256"}],"name":"repayAaveDebt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"asset","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"revokeApproval","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"assetIn","type":"address"},{"internalType":"contract ERC20","name":"assetOut","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"enum SwapRouter.Exchange","name":"exchange","type":"uint8"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"swap","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"tokenIn","type":"address"},{"internalType":"contract ERC20","name":"tokenToRepay","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"enum SwapRouter.Exchange","name":"exchange","type":"uint8"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"swapAndRepay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"withdraw","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"withdrawableFrom","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}]Contract Creation Code
608060405234801561001057600080fd5b50611aa1806100206000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80639a85e1d311610097578063d70c153911610066578063d70c1539146101de578063e170a9bf146101f1578063f2879a8d1461021c578063fa50e5d21461022f57600080fd5b80639a85e1d314610185578063aeffddde14610198578063c9111bd7146101b8578063d3bfe76a146101cb57600080fd5b806378415365116100d35780637841536514610148578063788fb4841461015b5780637998a1c41461016e57806389353a091461017657600080fd5b806317d964df146100fa578063642c2d8e1461012057806369445c3114610135575b600080fd5b61010d610108366004611121565b610242565b6040519081526020015b60405180910390f35b61013361012e3660046111b8565b610625565b005b610133610143366004611234565b610648565b61010d6101563660046112a0565b610661565b61013361016936600461136a565b6106ea565b61010d6107fd565b60405160018152602001610117565b610133610193366004611422565b61085b565b6101ab6101a63660046112a0565b610bd2565b604051610117919061144e565b6101336101c636600461149b565b610c35565b6101336101d936600461151a565b610c4e565b6101336101ec366004611422565b610c67565b6102046101ff3660046112a0565b610d37565b6040516001600160a01b039091168152602001610117565b61010d61022a3660046111b8565b610db2565b61010d61023d366004611553565b610f1a565b600061024e8786610f23565b94506000846001811115610264576102646115b6565b036102ae5760008380602001905181019061027f9190611630565b905080866000604051602001610297939291906116a8565b60405160208183030381529060405293505061032b565b60018460018111156102c2576102c26115b6565b0361031257600080848060200190518101906102de91906116d4565b9150915081818860006040516020016102fa94939291906117a0565b6040516020818303038152906040529450505061032b565b604051637467af4b60e01b815260040160405180910390fd5b6000306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa15801561036b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061038f919061180e565b604051635c9fcd8560e11b8152600160048201526001600160a01b03919091169063b93f9b0a90602401602060405180830381865afa1580156103d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103fa919061180e565b90506000306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa15801561043c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610460919061180e565b604051635c9fcd8560e11b8152600260048201526001600160a01b03919091169063b93f9b0a90602401602060405180830381865afa1580156104a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104cb919061180e565b90506104e16001600160a01b038a168389610fa4565b6040516302dfe16960e41b81526001600160a01b03831690632dfe169090610515908990899030908f908f90600401611871565b6020604051808303816000875af1158015610534573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061055891906118d2565b60405163151d4a5960e31b81526001600160a01b038b81166004830152602482018a90528a811660448301529194509082169063a8ea52c890606401602060405180830381865afa1580156105b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d591906118d2565b965060006105f5886001600160401b038716670de0b6b3a7640000611021565b9050808410156106185760405163a686627d60e01b815260040160405180910390fd5b5050509695505050505050565b60006106348686868686610db2565b90506106408582610c67565b505050505050565b604051633204ed5b60e21b815260040160405180910390fd5b60008082806020019051810190610678919061180e565b6040516370a0823160e01b81523360048201529091506001600160a01b038216906370a0823190602401602060405180830381865afa1580156106bf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e391906118d2565b9392505050565b81518351146107395760405162461bcd60e51b815260206004820152601660248201527524b7383aba103632b733ba341036b4b9b6b0ba31b41760511b60448201526064015b60405180910390fd5b600083516001600160401b038111156107545761075461106c565b60405190808252806020026020018201604052801561077d578160200160208202803683370190505b5060405163ab9c4b5d60e01b8152909150737d2768de32b0b80b7a3454c06bdac94a69ddc7a99063ab9c4b5d906107c590309088908890879084908a9060009060040161191b565b600060405180830381600087803b1580156107df57600080fd5b505af11580156107f3573d6000803e3d6000fd5b5050505050505050565b6000604051602001610840906020808252601c908201527f416176652064656274546f6b656e2041646170746f72205620302e3000000000604082015260600190565b60405160208183030381529060405280519060200120905090565b60006108656107fd565b604080516001600160a01b03861660208201526001910160408051601f198184030181529082905261089b939291602001611999565b6040516020818303038152906040528051906020012090506000306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610917919061180e565b6001600160a01b03166309f7abd2836040518263ffffffff1660e01b815260040161094491815260200190565b602060405180830381865afa158015610961573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061098591906119c3565b60405163024eefab60e61b815263ffffffff8216600482015290915030906393bbeac090602401602060405180830381865afa1580156109c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ed91906119e9565b610a1557604051633ba3976160e11b81526001600160a01b0385166004820152602401610730565b737d2768de32b0b80b7a3454c06bdac94a69ddc7a96001600160a01b031663a415bcad856001600160a01b031663b16a19de6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9a919061180e565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260248101869052600260448201526000606482015230608482015260a401600060405180830381600087803b158015610af657600080fd5b505af1158015610b0a573d6000803e3d6000fd5b505050506000610b2b737d2768de32b0b80b7a3454c06bdac94a69ddc7a990565b604051632fe4a15f60e21b81523060048201526001600160a01b03919091169063bf92857c9060240160c060405180830381865afa158015610b71573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b959190611a0b565b95505050505050610bab6710a741a46278000090565b811015610bcb576040516360f7f1fd60e11b815260040160405180910390fd5b5050505050565b60408051600180825281830190925260609160208083019080368337019050509050610bfd82610d37565b81600081518110610c1057610c10611a55565b60200260200101906001600160a01b031690816001600160a01b031681525050919050565b604051635f5003c560e11b815260040160405180910390fd5b610c636001600160a01b038316826000610fa4565b5050565b610c718282610f23565b9050610c9b6001600160a01b038316737d2768de32b0b80b7a3454c06bdac94a69ddc7a983610fa4565b737d2768de32b0b80b7a3454c06bdac94a69ddc7a960405163573ade8160e01b81526001600160a01b0384811660048301526024820184905260026044830152306064830152919091169063573ade81906084016020604051808303816000875af1158015610d0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d3291906118d2565b505050565b60008082806020019051810190610d4e919061180e565b9050806001600160a01b031663b16a19de6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d8e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e3919061180e565b600080306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa158015610df3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e17919061180e565b604051635c9fcd8560e11b8152600160048201526001600160a01b03919091169063b93f9b0a90602401602060405180830381865afa158015610e5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e82919061180e565b9050610e986001600160a01b0388168287610fa4565b6040516302dfe16960e41b81526001600160a01b03821690632dfe169090610ecc908790879030908d908d90600401611871565b6020604051808303816000875af1158015610eeb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f0f91906118d2565b979650505050505050565b60005b92915050565b60006000198203610f9d576040516370a0823160e01b81523060048201526001600160a01b038416906370a0823190602401602060405180830381865afa158015610f72573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f9691906118d2565b9050610f1d565b5080610f1d565b600060405163095ea7b360e01b8152836004820152826024820152602060006044836000895af13d15601f3d116001600051141617169150508061101b5760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b6044820152606401610730565b50505050565b82820281151584158583048514171661103957600080fd5b0492915050565b6001600160a01b038116811461105557600080fd5b50565b80356002811061106757600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156110aa576110aa61106c565b604052919050565b600082601f8301126110c357600080fd5b81356001600160401b038111156110dc576110dc61106c565b6110ef601f8201601f1916602001611082565b81815284602083860101111561110457600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060008060c0878903121561113a57600080fd5b863561114581611040565b9550602087013561115581611040565b94506040870135935061116a60608801611058565b925060808701356001600160401b038082111561118657600080fd5b6111928a838b016110b2565b935060a0890135915080821682146111a957600080fd5b50809150509295509295509295565b600080600080600060a086880312156111d057600080fd5b85356111db81611040565b945060208601356111eb81611040565b93506040860135925061120060608701611058565b915060808601356001600160401b0381111561121b57600080fd5b611227888289016110b2565b9150509295509295909350565b60008060006060848603121561124957600080fd5b8335925060208401356001600160401b038082111561126757600080fd5b611273878388016110b2565b9350604086013591508082111561128957600080fd5b50611296868287016110b2565b9150509250925092565b6000602082840312156112b257600080fd5b81356001600160401b038111156112c857600080fd5b6112d4848285016110b2565b949350505050565b60006001600160401b038211156112f5576112f561106c565b5060051b60200190565b600082601f83011261131057600080fd5b81356020611325611320836112dc565b611082565b82815260059290921b8401810191818101908684111561134457600080fd5b8286015b8481101561135f5780358352918301918301611348565b509695505050505050565b60008060006060848603121561137f57600080fd5b83356001600160401b038082111561139657600080fd5b818601915086601f8301126113aa57600080fd5b813560206113ba611320836112dc565b82815260059290921b8401810191818101908a8411156113d957600080fd5b948201945b838610156114005785356113f181611040565b825294820194908201906113de565b9750508701359250508082111561141657600080fd5b611273878388016112ff565b6000806040838503121561143557600080fd5b823561144081611040565b946020939093013593505050565b6020808252825182820181905260009190848201906040850190845b8181101561148f5783516001600160a01b03168352928401929184019160010161146a565b50909695505050505050565b600080600080608085870312156114b157600080fd5b8435935060208501356114c381611040565b925060408501356001600160401b03808211156114df57600080fd5b6114eb888389016110b2565b9350606087013591508082111561150157600080fd5b5061150e878288016110b2565b91505092959194509250565b6000806040838503121561152d57600080fd5b823561153881611040565b9150602083013561154881611040565b809150509250929050565b6000806040838503121561156657600080fd5b82356001600160401b038082111561157d57600080fd5b611589868387016110b2565b9350602085013591508082111561159f57600080fd5b506115ac858286016110b2565b9150509250929050565b634e487b7160e01b600052602160045260246000fd5b600082601f8301126115dd57600080fd5b815160206115ed611320836112dc565b82815260059290921b8401810191818101908684111561160c57600080fd5b8286015b8481101561135f57805161162381611040565b8352918301918301611610565b60006020828403121561164257600080fd5b81516001600160401b0381111561165857600080fd5b6112d4848285016115cc565b600081518084526020808501945080840160005b8381101561169d5781516001600160a01b031687529582019590820190600101611678565b509495945050505050565b6060815260006116bb6060830186611664565b905083602083015260ff83166040830152949350505050565b600080604083850312156116e757600080fd5b82516001600160401b03808211156116fe57600080fd5b61170a868387016115cc565b935060209150818501518181111561172157600080fd5b85019050601f8101861361173457600080fd5b8051611742611320826112dc565b81815260059190911b8201830190838101908883111561176157600080fd5b928401925b8284101561179157835162ffffff811681146117825760008081fd5b82529284019290840190611766565b80955050505050509250929050565b6080815260006117b36080830187611664565b82810360208481019190915286518083528782019282019060005b818110156117ef57845162ffffff16835293830193918301916001016117ce565b5050604085019690965250505060ff9190911660609091015292915050565b60006020828403121561182057600080fd5b81516106e381611040565b6000815180845260005b8181101561185157602081850181015186830182015201611835565b506000602082860101526020601f19601f83011685010191505092915050565b60006002871061189157634e487b7160e01b600052602160045260246000fd5b86825260a060208301526118a860a083018761182b565b6001600160a01b039586166040840152938516606083015250921660809092019190915292915050565b6000602082840312156118e457600080fd5b5051919050565b600081518084526020808501945080840160005b8381101561169d578151875295820195908201906001016118ff565b600060018060a01b03808a16835260e0602084015261193d60e084018a611664565b838103604085015261194f818a6118eb565b9050838103606085015261196381896118eb565b9050818716608085015283810360a085015261197f818761182b565b9250505061ffff831660c083015298975050505050505050565b83815282151560208201526060604082015260006119ba606083018461182b565b95945050505050565b6000602082840312156119d557600080fd5b815163ffffffff811681146106e357600080fd5b6000602082840312156119fb57600080fd5b815180151581146106e357600080fd5b60008060008060008060c08789031215611a2457600080fd5b865195506020870151945060408701519350606087015192506080870151915060a087015190509295509295509295565b634e487b7160e01b600052603260045260246000fdfea26469706673582212201bc563f86df7283aeb5248ab783543cfd18c449b262a393ad94057819ada8c1e64736f6c63430008100033
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100f55760003560e01c80639a85e1d311610097578063d70c153911610066578063d70c1539146101de578063e170a9bf146101f1578063f2879a8d1461021c578063fa50e5d21461022f57600080fd5b80639a85e1d314610185578063aeffddde14610198578063c9111bd7146101b8578063d3bfe76a146101cb57600080fd5b806378415365116100d35780637841536514610148578063788fb4841461015b5780637998a1c41461016e57806389353a091461017657600080fd5b806317d964df146100fa578063642c2d8e1461012057806369445c3114610135575b600080fd5b61010d610108366004611121565b610242565b6040519081526020015b60405180910390f35b61013361012e3660046111b8565b610625565b005b610133610143366004611234565b610648565b61010d6101563660046112a0565b610661565b61013361016936600461136a565b6106ea565b61010d6107fd565b60405160018152602001610117565b610133610193366004611422565b61085b565b6101ab6101a63660046112a0565b610bd2565b604051610117919061144e565b6101336101c636600461149b565b610c35565b6101336101d936600461151a565b610c4e565b6101336101ec366004611422565b610c67565b6102046101ff3660046112a0565b610d37565b6040516001600160a01b039091168152602001610117565b61010d61022a3660046111b8565b610db2565b61010d61023d366004611553565b610f1a565b600061024e8786610f23565b94506000846001811115610264576102646115b6565b036102ae5760008380602001905181019061027f9190611630565b905080866000604051602001610297939291906116a8565b60405160208183030381529060405293505061032b565b60018460018111156102c2576102c26115b6565b0361031257600080848060200190518101906102de91906116d4565b9150915081818860006040516020016102fa94939291906117a0565b6040516020818303038152906040529450505061032b565b604051637467af4b60e01b815260040160405180910390fd5b6000306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa15801561036b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061038f919061180e565b604051635c9fcd8560e11b8152600160048201526001600160a01b03919091169063b93f9b0a90602401602060405180830381865afa1580156103d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103fa919061180e565b90506000306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa15801561043c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610460919061180e565b604051635c9fcd8560e11b8152600260048201526001600160a01b03919091169063b93f9b0a90602401602060405180830381865afa1580156104a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104cb919061180e565b90506104e16001600160a01b038a168389610fa4565b6040516302dfe16960e41b81526001600160a01b03831690632dfe169090610515908990899030908f908f90600401611871565b6020604051808303816000875af1158015610534573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061055891906118d2565b60405163151d4a5960e31b81526001600160a01b038b81166004830152602482018a90528a811660448301529194509082169063a8ea52c890606401602060405180830381865afa1580156105b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d591906118d2565b965060006105f5886001600160401b038716670de0b6b3a7640000611021565b9050808410156106185760405163a686627d60e01b815260040160405180910390fd5b5050509695505050505050565b60006106348686868686610db2565b90506106408582610c67565b505050505050565b604051633204ed5b60e21b815260040160405180910390fd5b60008082806020019051810190610678919061180e565b6040516370a0823160e01b81523360048201529091506001600160a01b038216906370a0823190602401602060405180830381865afa1580156106bf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e391906118d2565b9392505050565b81518351146107395760405162461bcd60e51b815260206004820152601660248201527524b7383aba103632b733ba341036b4b9b6b0ba31b41760511b60448201526064015b60405180910390fd5b600083516001600160401b038111156107545761075461106c565b60405190808252806020026020018201604052801561077d578160200160208202803683370190505b5060405163ab9c4b5d60e01b8152909150737d2768de32b0b80b7a3454c06bdac94a69ddc7a99063ab9c4b5d906107c590309088908890879084908a9060009060040161191b565b600060405180830381600087803b1580156107df57600080fd5b505af11580156107f3573d6000803e3d6000fd5b5050505050505050565b6000604051602001610840906020808252601c908201527f416176652064656274546f6b656e2041646170746f72205620302e3000000000604082015260600190565b60405160208183030381529060405280519060200120905090565b60006108656107fd565b604080516001600160a01b03861660208201526001910160408051601f198184030181529082905261089b939291602001611999565b6040516020818303038152906040528051906020012090506000306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108f3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610917919061180e565b6001600160a01b03166309f7abd2836040518263ffffffff1660e01b815260040161094491815260200190565b602060405180830381865afa158015610961573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061098591906119c3565b60405163024eefab60e61b815263ffffffff8216600482015290915030906393bbeac090602401602060405180830381865afa1580156109c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ed91906119e9565b610a1557604051633ba3976160e11b81526001600160a01b0385166004820152602401610730565b737d2768de32b0b80b7a3454c06bdac94a69ddc7a96001600160a01b031663a415bcad856001600160a01b031663b16a19de6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9a919061180e565b6040516001600160e01b031960e084901b1681526001600160a01b03909116600482015260248101869052600260448201526000606482015230608482015260a401600060405180830381600087803b158015610af657600080fd5b505af1158015610b0a573d6000803e3d6000fd5b505050506000610b2b737d2768de32b0b80b7a3454c06bdac94a69ddc7a990565b604051632fe4a15f60e21b81523060048201526001600160a01b03919091169063bf92857c9060240160c060405180830381865afa158015610b71573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b959190611a0b565b95505050505050610bab6710a741a46278000090565b811015610bcb576040516360f7f1fd60e11b815260040160405180910390fd5b5050505050565b60408051600180825281830190925260609160208083019080368337019050509050610bfd82610d37565b81600081518110610c1057610c10611a55565b60200260200101906001600160a01b031690816001600160a01b031681525050919050565b604051635f5003c560e11b815260040160405180910390fd5b610c636001600160a01b038316826000610fa4565b5050565b610c718282610f23565b9050610c9b6001600160a01b038316737d2768de32b0b80b7a3454c06bdac94a69ddc7a983610fa4565b737d2768de32b0b80b7a3454c06bdac94a69ddc7a960405163573ade8160e01b81526001600160a01b0384811660048301526024820184905260026044830152306064830152919091169063573ade81906084016020604051808303816000875af1158015610d0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d3291906118d2565b505050565b60008082806020019051810190610d4e919061180e565b9050806001600160a01b031663b16a19de6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d8e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e3919061180e565b600080306001600160a01b0316637b1039996040518163ffffffff1660e01b8152600401602060405180830381865afa158015610df3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e17919061180e565b604051635c9fcd8560e11b8152600160048201526001600160a01b03919091169063b93f9b0a90602401602060405180830381865afa158015610e5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e82919061180e565b9050610e986001600160a01b0388168287610fa4565b6040516302dfe16960e41b81526001600160a01b03821690632dfe169090610ecc908790879030908d908d90600401611871565b6020604051808303816000875af1158015610eeb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f0f91906118d2565b979650505050505050565b60005b92915050565b60006000198203610f9d576040516370a0823160e01b81523060048201526001600160a01b038416906370a0823190602401602060405180830381865afa158015610f72573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f9691906118d2565b9050610f1d565b5080610f1d565b600060405163095ea7b360e01b8152836004820152826024820152602060006044836000895af13d15601f3d116001600051141617169150508061101b5760405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b6044820152606401610730565b50505050565b82820281151584158583048514171661103957600080fd5b0492915050565b6001600160a01b038116811461105557600080fd5b50565b80356002811061106757600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156110aa576110aa61106c565b604052919050565b600082601f8301126110c357600080fd5b81356001600160401b038111156110dc576110dc61106c565b6110ef601f8201601f1916602001611082565b81815284602083860101111561110457600080fd5b816020850160208301376000918101602001919091529392505050565b60008060008060008060c0878903121561113a57600080fd5b863561114581611040565b9550602087013561115581611040565b94506040870135935061116a60608801611058565b925060808701356001600160401b038082111561118657600080fd5b6111928a838b016110b2565b935060a0890135915080821682146111a957600080fd5b50809150509295509295509295565b600080600080600060a086880312156111d057600080fd5b85356111db81611040565b945060208601356111eb81611040565b93506040860135925061120060608701611058565b915060808601356001600160401b0381111561121b57600080fd5b611227888289016110b2565b9150509295509295909350565b60008060006060848603121561124957600080fd5b8335925060208401356001600160401b038082111561126757600080fd5b611273878388016110b2565b9350604086013591508082111561128957600080fd5b50611296868287016110b2565b9150509250925092565b6000602082840312156112b257600080fd5b81356001600160401b038111156112c857600080fd5b6112d4848285016110b2565b949350505050565b60006001600160401b038211156112f5576112f561106c565b5060051b60200190565b600082601f83011261131057600080fd5b81356020611325611320836112dc565b611082565b82815260059290921b8401810191818101908684111561134457600080fd5b8286015b8481101561135f5780358352918301918301611348565b509695505050505050565b60008060006060848603121561137f57600080fd5b83356001600160401b038082111561139657600080fd5b818601915086601f8301126113aa57600080fd5b813560206113ba611320836112dc565b82815260059290921b8401810191818101908a8411156113d957600080fd5b948201945b838610156114005785356113f181611040565b825294820194908201906113de565b9750508701359250508082111561141657600080fd5b611273878388016112ff565b6000806040838503121561143557600080fd5b823561144081611040565b946020939093013593505050565b6020808252825182820181905260009190848201906040850190845b8181101561148f5783516001600160a01b03168352928401929184019160010161146a565b50909695505050505050565b600080600080608085870312156114b157600080fd5b8435935060208501356114c381611040565b925060408501356001600160401b03808211156114df57600080fd5b6114eb888389016110b2565b9350606087013591508082111561150157600080fd5b5061150e878288016110b2565b91505092959194509250565b6000806040838503121561152d57600080fd5b823561153881611040565b9150602083013561154881611040565b809150509250929050565b6000806040838503121561156657600080fd5b82356001600160401b038082111561157d57600080fd5b611589868387016110b2565b9350602085013591508082111561159f57600080fd5b506115ac858286016110b2565b9150509250929050565b634e487b7160e01b600052602160045260246000fd5b600082601f8301126115dd57600080fd5b815160206115ed611320836112dc565b82815260059290921b8401810191818101908684111561160c57600080fd5b8286015b8481101561135f57805161162381611040565b8352918301918301611610565b60006020828403121561164257600080fd5b81516001600160401b0381111561165857600080fd5b6112d4848285016115cc565b600081518084526020808501945080840160005b8381101561169d5781516001600160a01b031687529582019590820190600101611678565b509495945050505050565b6060815260006116bb6060830186611664565b905083602083015260ff83166040830152949350505050565b600080604083850312156116e757600080fd5b82516001600160401b03808211156116fe57600080fd5b61170a868387016115cc565b935060209150818501518181111561172157600080fd5b85019050601f8101861361173457600080fd5b8051611742611320826112dc565b81815260059190911b8201830190838101908883111561176157600080fd5b928401925b8284101561179157835162ffffff811681146117825760008081fd5b82529284019290840190611766565b80955050505050509250929050565b6080815260006117b36080830187611664565b82810360208481019190915286518083528782019282019060005b818110156117ef57845162ffffff16835293830193918301916001016117ce565b5050604085019690965250505060ff9190911660609091015292915050565b60006020828403121561182057600080fd5b81516106e381611040565b6000815180845260005b8181101561185157602081850181015186830182015201611835565b506000602082860101526020601f19601f83011685010191505092915050565b60006002871061189157634e487b7160e01b600052602160045260246000fd5b86825260a060208301526118a860a083018761182b565b6001600160a01b039586166040840152938516606083015250921660809092019190915292915050565b6000602082840312156118e457600080fd5b5051919050565b600081518084526020808501945080840160005b8381101561169d578151875295820195908201906001016118ff565b600060018060a01b03808a16835260e0602084015261193d60e084018a611664565b838103604085015261194f818a6118eb565b9050838103606085015261196381896118eb565b9050818716608085015283810360a085015261197f818761182b565b9250505061ffff831660c083015298975050505050505050565b83815282151560208201526060604082015260006119ba606083018461182b565b95945050505050565b6000602082840312156119d557600080fd5b815163ffffffff811681146106e357600080fd5b6000602082840312156119fb57600080fd5b815180151581146106e357600080fd5b60008060008060008060c08789031215611a2457600080fd5b865195506020870151945060408701519350606087015192506080870151915060a087015190509295509295509295565b634e487b7160e01b600052603260045260246000fdfea26469706673582212201bc563f86df7283aeb5248ab783543cfd18c449b262a393ad94057819ada8c1e64736f6c63430008100033
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.