Feature Tip: Add private address tag to any address under My Name Tag !
More Info
Private Name Tags
ContractCreator
TokenTracker
Latest 25 from a total of 2,035 transactions
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Approve | 24393988 | 18 hrs ago | IN | 0 ETH | 0.00003336 | ||||
| Approve | 24393979 | 18 hrs ago | IN | 0 ETH | 0.00003667 | ||||
| Approve | 24393976 | 18 hrs ago | IN | 0 ETH | 0.00005805 | ||||
| Approve | 24392746 | 22 hrs ago | IN | 0 ETH | 0.00005911 | ||||
| Approve | 24392744 | 22 hrs ago | IN | 0 ETH | 0.00006868 | ||||
| Approve | 24370098 | 4 days ago | IN | 0 ETH | 0.00014613 | ||||
| Approve | 24369711 | 4 days ago | IN | 0 ETH | 0.00004622 | ||||
| Approve | 24358965 | 5 days ago | IN | 0 ETH | 0.00000238 | ||||
| Approve | 24314975 | 11 days ago | IN | 0 ETH | 0.00000363 | ||||
| Redeem | 24286994 | 15 days ago | IN | 0 ETH | 0.00136121 | ||||
| Approve | 24228012 | 23 days ago | IN | 0 ETH | 0.00000465 | ||||
| Approve | 24153209 | 34 days ago | IN | 0 ETH | 0.00009997 | ||||
| Redeem | 24129953 | 37 days ago | IN | 0 ETH | 0.00002766 | ||||
| Approve | 24117738 | 39 days ago | IN | 0 ETH | 0.00000422 | ||||
| Redeem | 24117489 | 39 days ago | IN | 0 ETH | 0.00012824 | ||||
| Withdraw | 24111776 | 40 days ago | IN | 0 ETH | 0.00136954 | ||||
| Approve | 24110857 | 40 days ago | IN | 0 ETH | 0.00010011 | ||||
| Redeem | 24082426 | 44 days ago | IN | 0 ETH | 0.00002851 | ||||
| Redeem | 24027647 | 51 days ago | IN | 0 ETH | 0.0000253 | ||||
| Approve | 24012252 | 54 days ago | IN | 0 ETH | 0.00000131 | ||||
| Approve | 24012200 | 54 days ago | IN | 0 ETH | 0.00000138 | ||||
| Approve | 23998366 | 55 days ago | IN | 0 ETH | 0.00000808 | ||||
| Redeem | 23990719 | 57 days ago | IN | 0 ETH | 0.00008528 | ||||
| Approve | 23955040 | 62 days ago | IN | 0 ETH | 0.0000033 | ||||
| Approve | 23875843 | 73 days ago | IN | 0 ETH | 0.00005493 |
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| 0x3d602d80 | 16986070 | 1037 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0x3a763a9db61f4c8b57d033ac11d74e5c9fb3314f
Contract Name:
CellarInitializableV2_2
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 { Cellar, Registry, ERC20, PriceRouter } from "src/base/Cellar.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
contract CellarInitializableV2_2 is Cellar, Initializable {
/**
* @notice Constructor is only called for the implementation contract,
* so it can be safely filled with mostly zero inputs.
*/
constructor(
Registry _registry
)
Cellar(
_registry,
ERC20(address(0)),
"",
"",
abi.encode(new uint32[](0), new uint32[](0), new bytes[](0), new bytes[](0), 0, address(0), 0, 0)
)
{}
/**
* @notice Initialize function called by factory contract immediately after deployment.
* @param params abi encoded parameter containing
* - Registry contract
* - ERC20 cellar asset
* - String name of cellar
* - String symbol of cellar
* - uint32 holding position
* - bytes holding position config
* - address strategist payout address
*/
function initialize(bytes calldata params) external initializer {
(
address _owner,
Registry _registry,
ERC20 _asset,
string memory _name,
string memory _symbol,
uint32 _holdingPosition,
bytes memory _holdingPositionConfig,
address _strategistPayout
) = abi.decode(params, (address, Registry, ERC20, string, string, uint32, bytes, address));
// Initialize Cellar
registry = _registry;
asset = _asset;
owner = _owner;
shareLockPeriod = MAXIMUM_SHARE_LOCK_PERIOD;
allowedRebalanceDeviation = 0.003e18;
priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
// Aave V3 pool contract on ETH Mainnet
aavePool = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
// Initialize ERC20
name = _name;
symbol = _symbol;
decimals = 18;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
// Initialize Reentrancy Guard
locked = 1;
// Initialize Holding Position.
_addPositionToCatalogue(_holdingPosition);
_addPosition(0, _holdingPosition, _holdingPositionConfig, false);
_setHoldingPosition(_holdingPosition);
// Initialize remaining values.
feeData.strategistPlatformCut = 0.8e18;
feeData.strategistPayoutAddress = _strategistPayout;
// feeData = FeeData({
// strategistPlatformCut: 0.8e18,
// platformFee: 0.005e18,
// lastAccrual: uint64(block.timestamp),
// strategistPayoutAddress: _strategistPayout
// });
}
}// 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.7.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized < type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Internal function that returns the initialized version. Returns `_initialized`
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Internal function that returns the initialized version. Returns `_initializing`
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// 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 {
if (id > 0) {
_checkOwner();
if (id >= nextId) revert Registry__ContractNotRegistered(id);
} else {
if (msg.sender != getAddress[0]) revert Registry__OnlyCallableByZeroId();
}
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++;
}
// ============================================= ADDRESS 0 LOGIC =============================================
/**
* Address 0 is the address of the gravity bridge, and special abilities that the owner does not have.
* - It can change what address is stored at address 0.
* - It can change the owner of this contract.
*/
/**
* @notice Emitted when an ownership transition is started.
*/
event OwnerTransitionStarted(address newOwner, uint256 startTime);
/**
* @notice Emitted when an ownership transition is cancelled.
*/
event OwnerTransitionCancelled();
/**
* @notice Emitted when an ownership transition is completed.
*/
event OwnerTransitionComplete(address newOwner);
/**
* @notice Attempted to call a function intended for Zero Id address.
*/
error Registry__OnlyCallableByZeroId();
/**
* @notice Attempted to transition owner to the zero address.
*/
error Registry__NewOwnerCanNotBeZero();
/**
* @notice Attempted to perform a restricted action while ownership transition is pending.
*/
error Registry__TransitionPending();
/**
* @notice Attempted to cancel or complete a transition when one is not active.
*/
error Registry__TransitionNotPending();
/**
* @notice Attempted to call `completeTransition` from an address that is not the pending owner.
*/
error Registry__OnlyCallableByPendingOwner();
/**
* @notice The amount of time it takes for an ownership transition to work.
*/
uint256 public constant TRANSITION_PERIOD = 7 days;
/**
* @notice The Pending Owner, that becomes the owner after the transition period, and they call `completeTransition`.
*/
address public pendingOwner;
/**
* @notice The starting time stamp of the transition.
*/
uint256 public transitionStart;
/**
* @notice Allows Zero Id address to set a new owner, after the transition period is up.
*/
function transitionOwner(address newOwner) external {
if (msg.sender != getAddress[0]) revert Registry__OnlyCallableByZeroId();
if (pendingOwner != address(0)) revert Registry__TransitionPending();
if (newOwner == address(0)) revert Registry__NewOwnerCanNotBeZero();
pendingOwner = newOwner;
transitionStart = block.timestamp;
}
/**
* @notice Allows Zero Id address to cancel an ongoing owner transition.
*/
function cancelTransition() external {
if (msg.sender != getAddress[0]) revert Registry__OnlyCallableByZeroId();
if (pendingOwner == address(0)) revert Registry__TransitionNotPending();
pendingOwner = address(0);
transitionStart = 0;
}
/**
* @notice Allows pending owner to complete the ownership transition.
*/
function completeTransition() external {
if (pendingOwner == address(0)) revert Registry__TransitionNotPending();
if (msg.sender != pendingOwner) revert Registry__OnlyCallableByPendingOwner();
if (block.timestamp < transitionStart + TRANSITION_PERIOD) revert Registry__TransitionPending();
_transferOwnership(pendingOwner);
pendingOwner = address(0);
transitionStart = 0;
}
/**
* @notice Extends OZ Ownable `_checkOwner` function to block owner calls, if there is an ongoing transition.
*/
function _checkOwner() internal view override {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
if (transitionStart != 0) revert Registry__TransitionPending();
}
// ============================================ PAUSE LOGIC ============================================
/**
* @notice Emitted when a target is paused.
*/
event TargetPaused(address target);
/**
* @notice Emitted when a target is unpaused.
*/
event TargetUnpaused(address target);
/**
* @notice Attempted to unpause a target that was not paused.
*/
error Registry__TargetNotPaused(address target);
/**
* @notice Attempted to pause a target that was already paused.
*/
error Registry__TargetAlreadyPaused(address target);
/**
* @notice Mapping stores whether or not a cellar is paused.
*/
mapping(address => bool) public isCallerPaused;
/**
* @notice Allows multisig to pause multiple cellars in a single call.
*/
function batchPause(address[] calldata targets) external onlyOwner {
for (uint256 i; i < targets.length; ++i) _pauseTarget(targets[i]);
}
/**
* @notice Allows multisig to unpause multiple cellars in a single call.
*/
function batchUnpause(address[] calldata targets) external onlyOwner {
for (uint256 i; i < targets.length; ++i) _unpauseTarget(targets[i]);
}
/**
* @notice Helper function to pause some target.
*/
function _pauseTarget(address target) internal {
if (isCallerPaused[target]) revert Registry__TargetAlreadyPaused(target);
isCallerPaused[target] = true;
emit TargetPaused(target);
}
/**
* @notice Helper function to unpause some target.
*/
function _unpauseTarget(address target) internal {
if (!isCallerPaused[target]) revert Registry__TargetNotPaused(target);
isCallerPaused[target] = false;
emit TargetUnpaused(target);
}
// ============================================ 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(address adaptor);
/**
* @notice Attempted to trust an already trusted adaptor.
*/
error Registry__AdaptorAlreadyTrusted(address adaptor);
/**
* @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 identfier to bool, to track if the identifier 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
*/
function trustAdaptor(address adaptor) external onlyOwner {
if (isAdaptorTrusted[adaptor]) revert Registry__AdaptorAlreadyTrusted(adaptor);
bytes32 identifier = BaseAdaptor(adaptor).identifier();
if (isIdentifierUsed[identifier]) revert Registry__IdentifierNotUnique();
isAdaptorTrusted[adaptor] = true;
isIdentifierUsed[identifier] = true;
}
/**
* @notice Allows registry to distrust adaptors.
* @dev Doing so prevents Cellars from adding this adaptor to their catalogue.
*/
function distrustAdaptor(address adaptor) external onlyOwner {
if (!isAdaptorTrusted[adaptor]) revert Registry__AdaptorNotTrusted(adaptor);
// Set trust to false.
isAdaptorTrusted[adaptor] = false;
// We are NOT resetting `isIdentifierUsed` because if this adaptor is distrusted, then something needs
// to change about the new one being re-trusted.
}
/**
* @notice Reverts if `adaptor` is not trusted by the registry.
*/
function revertIfAdaptorIsNotTrusted(address adaptor) external view {
if (!isAdaptorTrusted[adaptor]) revert Registry__AdaptorNotTrusted(adaptor);
}
// ============================================ 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 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 that does not exist.
*/
error Registry__PositionDoesNotExist();
/**
* @notice Attempted to add a position that is not trusted.
*/
error Registry__PositionIsNotTrusted(uint32 position);
/**
* @notice Addresses of the positions currently used by the cellar.
*/
uint256 public constant PRICE_ROUTER_REGISTRY_SLOT = 2;
/**
* @notice Stores the number of positions that have been added to the registry.
* Starts at 101.
*/
uint32 public positionCount = 100;
/**
* @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 Maps a position to a bool indicating whether or not it is trusted.
*/
mapping(uint32 => bool) public isPositionTrusted;
/**
* @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
* @return positionId the position id of the newly added position
*/
function trustPosition(address adaptor, bytes memory adaptorData) 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(adaptor);
// Set position data.
getPositionIdToPositionData[positionId] = PositionData({
adaptor: adaptor,
isDebt: isDebt,
adaptorData: adaptorData,
configurationData: abi.encode(0)
});
// Globally trust the position.
isPositionTrusted[positionId] = true;
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 Allows registry to distrust positions.
* @dev Doing so prevents Cellars from adding this position to their catalogue,
* and adding the position to their tracked arrays.
*/
function distrustPosition(uint32 positionId) external onlyOwner {
if (!isPositionTrusted[positionId]) revert("Position not trusted");
isPositionTrusted[positionId] = false;
}
/**
* @notice Called by Cellars to add a new position to themselves.
* @param positionId the id of the position the cellar wants to add
* @return adaptor the address of the adaptor, isDebt bool indicating whether position is
* debt or not, and adaptorData needed to interact with position
*/
function addPositionToCellar(
uint32 positionId
) external view returns (address adaptor, bool isDebt, bytes memory adaptorData) {
if (positionId > positionCount || positionId == 0) revert Registry__PositionDoesNotExist();
revertIfPositionIsNotTrusted(positionId);
PositionData memory positionData = getPositionIdToPositionData[positionId];
return (positionData.adaptor, positionData.isDebt, positionData.adaptorData);
}
/**
* @notice Reverts if `positionId` is not trusted by the registry.
*/
function revertIfPositionIsNotTrusted(uint32 positionId) public view {
if (!isPositionTrusted[positionId]) revert Registry__PositionIsNotTrusted(positionId);
}
}// 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;
// ========================================= MULTICALL =========================================
/**
* @notice Allows caller to call multiple functions in a single TX.
* @dev Does NOT return the function return values.
*/
function multicall(bytes[] calldata data) external {
for (uint256 i = 0; i < data.length; i++) address(this).functionDelegateCall(data[i]);
}
// ========================================= 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;
}
// ========================================= PRICE ROUTER CACHE =========================================
/**
* @notice Cached price router contract.
* @dev This way cellar has to "opt in" to price router changes.
*/
PriceRouter public priceRouter;
/**
* @notice Updates the cellar to use the lastest price router in the registry.
* @param checkTotalAssets If true totalAssets is checked before and after updating the price router,
* and is verified to be withing a +- 5% envelope.
* If false totalAssets is only called after updating the price router.]
* @param allowableRange The +- range the total assets may deviate between the old and new price router.
* - 1_000 == 10%
* - 500 == 5%
* @dev `allowableRange` reverts from arithmetic underflow if it is greater than 10_000, this is
* desired behavior.
*/
function cachePriceRouter(bool checkTotalAssets, uint16 allowableRange) external onlyOwner {
uint256 minAssets;
uint256 maxAssets;
if (checkTotalAssets) {
uint256 assetsBefore = totalAssets();
minAssets = assetsBefore.mulDivDown(1e4 - allowableRange, 1e4);
maxAssets = assetsBefore.mulDivDown(1e4 + allowableRange, 1e4);
}
priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
uint256 assetsAfter = totalAssets();
if (checkTotalAssets) {
if (assetsAfter < minAssets || assetsAfter > maxAssets)
revert Cellar__TotalAssetDeviatedOutsideRange(assetsAfter, minAssets, maxAssets);
}
}
// ========================================= 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 Emitted when Governance adds/removes a position to/from the cellars catalogue.
*/
event PositionCatalogueAltered(uint32 positionId, bool inCatalogue);
/**
* @notice Emitted when Governance adds/removes an adaptor to/from the cellars catalogue.
*/
event AdaptorCatalogueAltered(address adaptor, bool inCatalogue);
/**
* @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 to add a position that is not in the catalogue.
* @param position id of the position
*/
error Cellar__PositionNotInCatalogue(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 Positions the strategist is approved to use without any governance intervention.
*/
mapping(uint32 => bool) public positionCatalogue;
/**
* @notice Adaptors the strategist is approved to use without any governance intervention.
*/
mapping(address => bool) public adaptorCatalogue;
/**
* @notice Allows Governance to add positions to this cellar's catalogue.
*/
function addPositionToCatalogue(uint32 positionId) external onlyOwner {
_addPositionToCatalogue(positionId);
}
/**
* @notice Helper function that checks the position is trusted.
*/
function _addPositionToCatalogue(uint32 positionId) internal {
// Make sure position is not paused and is trusted.
registry.revertIfPositionIsNotTrusted(positionId);
positionCatalogue[positionId] = true;
emit PositionCatalogueAltered(positionId, true);
}
/**
* @notice Allows Governance to remove positions from this cellar's catalogue.
*/
function removePositionFromCatalogue(uint32 positionId) external onlyOwner {
positionCatalogue[positionId] = false;
emit PositionCatalogueAltered(positionId, false);
}
/**
* @notice Allows Governance to add adaptors to this cellar's catalogue.
*/
function addAdaptorToCatalogue(address adaptor) external onlyOwner {
// Make sure adaptor is not paused and is trusted.
registry.revertIfAdaptorIsNotTrusted(adaptor);
adaptorCatalogue[adaptor] = true;
emit AdaptorCatalogueAltered(adaptor, true);
}
/**
* @notice Allows Governance to remove adaptors from this cellar's catalogue.
*/
function removeAdaptorFromCatalogue(address adaptor) external onlyOwner {
adaptorCatalogue[adaptor] = false;
emit AdaptorCatalogueAltered(adaptor, false);
}
/**
* @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 is 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);
// Check if position is in the position catalogue.
if (!positionCatalogue[positionId]) revert Cellar__PositionNotInCatalogue(positionId);
// Grab position data from registry.
// Also checks if position is not trusted and reverts if so.
(address adaptor, bool isDebt, bytes memory adaptorData) = registry.addPositionToCellar(positionId);
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.
* @dev Called by strategist.
* @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];
// 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);
_removePosition(index, positionId, inDebtArray);
}
/**
* @notice Allows Governance to force a cellar out of a position without making ANY external calls.
*/
function forcePositionOut(uint32 index, uint32 positionId, bool inDebtArray) external onlyOwner {
_removePosition(index, positionId, inDebtArray);
}
/**
* @notice Internal helper function to remove positions from cellars tracked arrays.
*/
function _removePosition(uint32 index, uint32 positionId, bool inDebtArray) internal {
if (positionId == holdingPosition) revert Cellar__RemovingHoldingPosition();
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 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 Attempted to interact with the cellar when it is paused.
*/
error Cellar__Paused();
/**
* @notice Whether or not the contract is shutdown in case of an emergency.
*/
bool public isShutdown;
/**
* @notice Pauses all user entry/exits, and strategist rebalances.
*/
bool public ignorePause;
/**
* @notice View function external contracts can use to see if the cellar is paused.
*/
function isPaused() external view returns (bool) {
if (!ignorePause) {
return registry.isCallerPaused(address(this));
}
return false;
}
/**
* @notice Pauses all user entry/exits, and strategist rebalances.
*/
function _checkIfPaused() internal view {
if (!ignorePause) {
if (registry.isCallerPaused(address(this))) revert Cellar__Paused();
}
}
/**
* @notice Allows governance to choose whether or not to respect a pause.
*/
function toggleIgnorePause(bool toggle) external onlyOwner {
ignorePause = toggle;
}
/**
* @notice Prevent a function from being called during a shutdown.
*/
function _whenNotShutdown() internal view {
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 onlyOwner {
_whenNotShutdown();
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;
/**
* @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;
priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
{
(
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) {
_addPositionToCatalogue(_creditPositions[i]);
_addPosition(i, _creditPositions[i], _creditConfigurationData[i], false);
}
for (uint32 i; i < _debtPositions.length; ++i) {
_addPositionToCatalogue(_debtPositions[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, , ) = abi.decode(
params,
(uint32[], uint32[], bytes[], bytes[], uint8, address, uint128, uint128)
);
feeData.strategistPayoutAddress = _strategistPayout;
}
// =========================================== 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();
_checkIfPaused();
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 {
_checkIfPaused();
// 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 {
// 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);
// 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) {
_checkIfPaused();
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) {
_checkIfPaused();
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) {
_checkIfPaused();
// 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 is either paused or is not trusted by governance.
* @param adaptor the adaptor address that is paused or not trusted.
*/
error Cellar__CallToAdaptorNotAllowed(address adaptor);
/**
* @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;
}
event AdaptorCalled(address adaptor, bytes data);
/**
* @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 nonReentrant {
_whenNotShutdown();
_checkIfPaused();
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;
// Revert if adaptor not in catalogue, or adaptor is paused.
if (!adaptorCatalogue[adaptor]) revert Cellar__CallToAdaptorNotAllowed(adaptor);
for (uint8 j = 0; j < data[i].callData.length; j++) {
adaptor.functionDelegateCall(data[i].callData[j]);
emit AdaptorCalled(adaptor, 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 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;
// Revert if adaptor not in catalogue, or adaptor is paused.
if (!adaptorCatalogue[adaptor]) revert Cellar__CallToAdaptorNotAllowed(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;
}
// ========================================== 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]);
}
}
function viewPositionBalances()
external
view
returns (ERC20[] memory assets, uint256[] memory balances, bool[] memory isDebt)
{
uint256 creditLen = creditPositions.length;
uint256 debtLen = debtPositions.length;
assets = new ERC20[](creditLen + debtLen);
balances = new uint256[](creditLen + debtLen);
isDebt = new bool[](creditLen + debtLen);
for (uint256 i = 0; i < creditLen; ++i) {
assets[i] = _assetOf(creditPositions[i]);
balances[i] = _balanceOf(creditPositions[i]);
isDebt[i] = false;
}
for (uint256 i = 0; i < debtLen; ++i) {
assets[i + creditPositions.length] = _assetOf(debtPositions[i]);
balances[i + creditPositions.length] = _balanceOf(debtPositions[i]);
isDebt[i + creditPositions.length] = true;
}
}
}// 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: 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: 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();
/**
* @notice Attempted swap has bad slippage.
*/
error BaseAdaptor__Slippage();
/**
* @notice Attempted swap used unsupported output asset.
*/
error BaseAdaptor__PricingNotSupported(address asset);
//============================================ 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;
}
/**
* @notice Max possible slippage when making a swap router swap.
*/
function slippage() public pure returns (uint32) {
return 0.9e4;
}
//============================================ 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 checks if `spender` has any more approval for `asset`, and if so revokes it.
*/
function _revokeExternalApproval(ERC20 asset, address spender) internal {
if (asset.allowance(address(this), spender) > 0) asset.safeApprove(spender, 0);
}
/**
* @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 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/",
"@ds-test/=lib/forge-std/lib/ds-test/src/",
"@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/integration-tests/contracts/ethereum/src/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"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 ABI
API[{"inputs":[{"internalType":"contract Registry","name":"_registry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"address","name":"expectedAsset","type":"address"}],"name":"Cellar__AssetMismatch","type":"error"},{"inputs":[{"internalType":"address","name":"adaptor","type":"address"}],"name":"Cellar__CallToAdaptorNotAllowed","type":"error"},{"inputs":[],"name":"Cellar__CallerNotAavePool","type":"error"},{"inputs":[],"name":"Cellar__ContractNotShutdown","type":"error"},{"inputs":[],"name":"Cellar__ContractShutdown","type":"error"},{"inputs":[{"internalType":"uint32","name":"position","type":"uint32"}],"name":"Cellar__DebtMismatch","type":"error"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"uint256","name":"maxDeposit","type":"uint256"}],"name":"Cellar__DepositRestricted","type":"error"},{"inputs":[],"name":"Cellar__ExternalInitiator","type":"error"},{"inputs":[{"internalType":"address","name":"illiquidPosition","type":"address"}],"name":"Cellar__IlliquidWithdraw","type":"error"},{"inputs":[{"internalType":"uint256","name":"assetsOwed","type":"uint256"}],"name":"Cellar__IncompleteWithdraw","type":"error"},{"inputs":[],"name":"Cellar__InvalidFee","type":"error"},{"inputs":[],"name":"Cellar__InvalidFeeCut","type":"error"},{"inputs":[{"internalType":"uint32","name":"positionId","type":"uint32"}],"name":"Cellar__InvalidHoldingPosition","type":"error"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"max","type":"uint256"}],"name":"Cellar__InvalidRebalanceDeviation","type":"error"},{"inputs":[],"name":"Cellar__InvalidShareLockPeriod","type":"error"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"}],"name":"Cellar__NotApprovedToDepositOnBehalf","type":"error"},{"inputs":[],"name":"Cellar__Paused","type":"error"},{"inputs":[{"internalType":"uint32","name":"position","type":"uint32"}],"name":"Cellar__PositionAlreadyUsed","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxPositions","type":"uint256"}],"name":"Cellar__PositionArrayFull","type":"error"},{"inputs":[{"internalType":"uint32","name":"position","type":"uint32"},{"internalType":"uint256","name":"sharesRemaining","type":"uint256"}],"name":"Cellar__PositionNotEmpty","type":"error"},{"inputs":[{"internalType":"uint32","name":"position","type":"uint32"}],"name":"Cellar__PositionNotInCatalogue","type":"error"},{"inputs":[{"internalType":"uint32","name":"position","type":"uint32"}],"name":"Cellar__PositionNotUsed","type":"error"},{"inputs":[],"name":"Cellar__RemovingHoldingPosition","type":"error"},{"inputs":[{"internalType":"uint256","name":"timeSharesAreUnlocked","type":"uint256"},{"internalType":"uint256","name":"currentBlock","type":"uint256"}],"name":"Cellar__SharesAreLocked","type":"error"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"uint256","name":"min","type":"uint256"},{"internalType":"uint256","name":"max","type":"uint256"}],"name":"Cellar__TotalAssetDeviatedOutsideRange","type":"error"},{"inputs":[{"internalType":"uint256","name":"current","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"Cellar__TotalSharesMustRemainConstant","type":"error"},{"inputs":[],"name":"Cellar__ZeroAssets","type":"error"},{"inputs":[],"name":"Cellar__ZeroShares","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"adaptor","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"AdaptorCalled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"adaptor","type":"address"},{"indexed":false,"internalType":"bool","name":"inCatalogue","type":"bool"}],"name":"AdaptorCatalogueAltered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"oldPlatformFee","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newPlatformFee","type":"uint64"}],"name":"PlatformFeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"position","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"PositionAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"positionId","type":"uint32"},{"indexed":false,"internalType":"bool","name":"inCatalogue","type":"bool"}],"name":"PositionCatalogueAltered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"position","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"PositionRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"newPosition1","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"newPosition2","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"index1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index2","type":"uint256"}],"name":"PositionSwapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldDeviation","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newDeviation","type":"uint256"}],"name":"RebalanceDeviationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldPeriod","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newPeriod","type":"uint256"}],"name":"ShareLockingPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isShutdown","type":"bool"}],"name":"ShutdownChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldPayoutAddress","type":"address"},{"indexed":false,"internalType":"address","name":"newPayoutAddress","type":"address"}],"name":"StrategistPayoutAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"oldPlatformCut","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newPlatformCut","type":"uint64"}],"name":"StrategistPlatformCutChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GRAVITY_BRIDGE_REGISTRY_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAXIMUM_SHARE_LOCK_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_FEE_CUT","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PLATFORM_FEE","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_POSITIONS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_REBALANCE_DEVIATION","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_SHARE_LOCK_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICE_ROUTER_REGISTRY_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aavePool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"adaptorCatalogue","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"adaptor","type":"address"}],"name":"addAdaptorToCatalogue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"index","type":"uint32"},{"internalType":"uint32","name":"positionId","type":"uint32"},{"internalType":"bytes","name":"configurationData","type":"bytes"},{"internalType":"bool","name":"inDebtArray","type":"bool"}],"name":"addPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"positionId","type":"uint32"}],"name":"addPositionToCatalogue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allowedRebalanceDeviation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockExternalReceiver","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"checkTotalAssets","type":"bool"},{"internalType":"uint16","name":"allowableRange","type":"uint16"}],"name":"cachePriceRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"adaptor","type":"address"},{"internalType":"bytes[]","name":"callData","type":"bytes[]"}],"internalType":"struct Cellar.AdaptorCall[]","name":"data","type":"tuple[]"}],"name":"callOnAdaptor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"creditPositions","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"debtPositions","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"premiums","type":"uint256[]"},{"internalType":"address","name":"initiator","type":"address"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"executeOperation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeData","outputs":[{"internalType":"uint64","name":"strategistPlatformCut","type":"uint64"},{"internalType":"uint64","name":"platformFee","type":"uint64"},{"internalType":"uint64","name":"lastAccrual","type":"uint64"},{"internalType":"address","name":"strategistPayoutAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"index","type":"uint32"},{"internalType":"uint32","name":"positionId","type":"uint32"},{"internalType":"bool","name":"inDebtArray","type":"bool"}],"name":"forcePositionOut","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCreditPositions","outputs":[{"internalType":"uint32[]","name":"","type":"uint32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDebtPositions","outputs":[{"internalType":"uint32[]","name":"","type":"uint32[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPositionAssets","outputs":[{"internalType":"contract ERC20[]","name":"assets","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"getPositionData","outputs":[{"internalType":"address","name":"adaptor","type":"address"},{"internalType":"bool","name":"isDebt","type":"bool"},{"internalType":"bytes","name":"adaptorData","type":"bytes"},{"internalType":"bytes","name":"configurationData","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"holdingPosition","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ignorePause","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"params","type":"bytes"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initiateShutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"isPositionUsed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isShutdown","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liftShutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"locked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"positionCatalogue","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceRouter","outputs":[{"internalType":"contract PriceRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"registry","outputs":[{"internalType":"contract Registry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"adaptor","type":"address"}],"name":"removeAdaptorFromCatalogue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"index","type":"uint32"},{"internalType":"bool","name":"inDebtArray","type":"bool"}],"name":"removePosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"positionId","type":"uint32"}],"name":"removePositionFromCatalogue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"positionId","type":"uint32"}],"name":"setHoldingPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newDeviation","type":"uint256"}],"name":"setRebalanceDeviation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLock","type":"uint256"}],"name":"setShareLockPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"payout","type":"address"}],"name":"setStrategistPayoutAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"cut","type":"uint64"}],"name":"setStrategistPlatformCut","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shareLockPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"index1","type":"uint32"},{"internalType":"uint32","name":"index2","type":"uint32"},{"internalType":"bool","name":"inDebtArray","type":"bool"}],"name":"swapPositions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"toggle","type":"bool"}],"name":"toggleIgnorePause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssetsWithdrawable","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userShareLockStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"viewPositionBalances","outputs":[{"internalType":"contract ERC20[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"balances","type":"uint256[]"},{"internalType":"bool[]","name":"isDebt","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
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.