// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.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://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev 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) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @notice A library to extend the address array data type.
*/
library AddressArray {
// =========================================== ADDRESS STORAGE ===========================================
/**
* @notice Add an address to the array at a given index.
* @param array address array to add the address to
* @param index index to add the address at
* @param value address to add to the array
*/
function add(
address[] storage array,
uint256 index,
address 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 an address from the array at a given index.
* @param array address array to remove the address from
* @param index index to remove the address at
*/
function remove(address[] storage array, uint256 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 Remove the first occurrence of a value in an array.
* @param array address array to remove the address from
* @param value address to remove from the array
*/
function remove(address[] storage array, address value) internal {
uint256 len = array.length;
for (uint256 i; i < len; i++)
if (array[i] == value) {
for (i; i < len - 1; i++) array[i] = array[i + 1];
array.pop();
return;
}
revert("Value not found");
}
/**
* @notice Check whether an array contains an address.
* @param array address array to check
* @param value address to check for
*/
function contains(address[] storage array, address value) internal view returns (bool) {
for (uint256 i; i < array.length; i++) if (value == array[i]) return true;
return false;
}
}
// 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);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
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: Apache-2.0
pragma solidity 0.8.16;
import { ERC4626, SafeERC20 } from "./ERC4626.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Registry } from "src/Registry.sol";
import { SwapRouter } from "src/modules/swap-router/SwapRouter.sol";
import { PriceRouter } from "src/modules/price-router/PriceRouter.sol";
import { IGravity } from "src/interfaces/external/IGravity.sol";
import { AddressArray } from "src/utils/AddressArray.sol";
import { Math } from "../utils/Math.sol";
// import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { Owned } from "@solmate/auth/Owned.sol";
import { ReentrancyGuard } from "@solmate/utils/ReentrancyGuard.sol";
/**
* @title Sommelier Cellar
* @notice A composable ERC4626 that can use a set of other ERC4626 or ERC20 positions to earn yield.
* @author Brian Le
*/
contract Cellar is ERC4626, Owned, ReentrancyGuard {
using AddressArray for address[];
using AddressArray for ERC20[];
using SafeERC20 for ERC20;
using SafeCast for uint256;
using Math for uint256;
// ========================================= POSITIONS CONFIG =========================================
/**
* @notice Emitted when a position is added.
* @param position address of position that was added
* @param index index that position was added at
*/
event PositionAdded(address indexed position, uint256 index);
/**
* @notice Emitted when a position is removed.
* @param position address of position that was removed
* @param index index that position was removed from
*/
event PositionRemoved(address indexed position, uint256 index);
/**
* @notice Emitted when the positions at two indexes are swapped.
* @param newPosition1 address of position (previously at index2) that replaced index1.
* @param newPosition2 address 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(address indexed newPosition1, address indexed newPosition2, uint256 index1, uint256 index2);
/**
* @notice Attempted an operation on an untrusted position.
* @param position address of the position
*/
error Cellar__UntrustedPosition(address position);
/**
* @notice Attempted to add a position that is already being used.
* @param position address of the position
*/
error Cellar__PositionAlreadyUsed(address 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(address 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 an action on a position that is not being used by the cellar but must be for
* the operation to succeed.
* @param position address of the invalid position
*/
error Cellar__InvalidPosition(address position);
/**
* @notice Attempted to remove holding position.
*/
error Cellar__RemoveHoldingPosition();
/**
* @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 Value specifying the interface a position uses.
* @param ERC20 an ERC20 token
* @param ERC4626 an ERC4626 vault
* @param Cellar a cellar
*/
enum PositionType {
ERC20,
ERC4626,
Cellar
}
/**
* @notice Addresses of the positions currently used by the cellar.
*/
address[] public positions;
/**
* @notice Tell whether a position is currently used.
*/
mapping(address => bool) public isPositionUsed;
/**
* @notice Get the type related to a position.
*/
mapping(address => PositionType) public getPositionType;
/**
* @notice Get the addresses of the positions current used by the cellar.
*/
function getPositions() external view returns (address[] memory) {
return positions;
}
/**
* @notice Maximum amount of positions a cellar can use at once.
*/
uint8 public constant MAX_POSITIONS = 32;
/**
* @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 position address of position to add
*/
function addPosition(uint256 index, address position) external onlyOwner whenNotShutdown {
if (positions.length >= MAX_POSITIONS) revert Cellar__PositionArrayFull(MAX_POSITIONS);
if (!isTrusted[position]) revert Cellar__UntrustedPosition(position);
// Check if position is already being used.
if (isPositionUsed[position]) revert Cellar__PositionAlreadyUsed(position);
// Add new position at a specified index.
positions.add(index, position);
isPositionUsed[position] = true;
emit PositionAdded(position, index);
}
/**
* @notice Push a trusted position to the end of the list of positions used by the cellar.
* @dev If you know you are going to add a position to the end of the array, this is more
* efficient then `addPosition`.
* @param position address of position to add
*/
function pushPosition(address position) external onlyOwner whenNotShutdown {
if (positions.length >= MAX_POSITIONS) revert Cellar__PositionArrayFull(MAX_POSITIONS);
if (!isTrusted[position]) revert Cellar__UntrustedPosition(position);
// Check if position is already being used.
if (isPositionUsed[position]) revert Cellar__PositionAlreadyUsed(position);
// Add new position to the end of the positions.
positions.push(position);
isPositionUsed[position] = true;
emit PositionAdded(position, positions.length - 1);
}
/**
* @notice Remove the position at a given index from the list of positions used by the cellar.
* @param index index at which to remove the position
*/
function removePosition(uint256 index) external onlyOwner {
// Get position being removed.
address position = positions[index];
// Only remove position if it is empty, and if it is not the holding position.
uint256 positionBalance = _balanceOf(position);
if (positionBalance > 0) revert Cellar__PositionNotEmpty(position, positionBalance);
if (position == holdingPosition) revert Cellar__RemoveHoldingPosition();
// Remove position at the given index.
positions.remove(index);
isPositionUsed[position] = false;
emit PositionRemoved(position, 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
*/
function swapPositions(uint256 index1, uint256 index2) external onlyOwner {
// Get the new positions that will be at each index.
address newPosition1 = positions[index2];
address newPosition2 = positions[index1];
// Swap positions.
(positions[index1], positions[index2]) = (newPosition1, newPosition2);
emit PositionSwapped(newPosition1, newPosition2, index1, index2);
}
// ============================================ TRUST CONFIG ============================================
/**
* @notice Emitted when trust for a position is changed.
* @param position address of position that trust was changed for
* @param isTrusted whether the position is trusted
*/
event TrustChanged(address indexed position, bool isTrusted);
/**
* @notice Attempted to trust a position not being used.
* @param position address of the invalid position
*/
error Cellar__PositionPricingNotSetUp(address position);
/**
* @notice Addresses of the positions currently used by the cellar.
*/
uint256 public constant PRICE_ROUTER_REGISTRY_SLOT = 2;
/**
* @notice Tell whether a position is trusted.
*/
mapping(address => bool) public isTrusted;
/**
* @notice Trust a position to be used by the cellar.
* @param position address of position to trust
* @param positionType value specifying the interface the position uses
*/
function trustPosition(address position, PositionType positionType) external onlyOwner {
// Trust position.
isTrusted[position] = true;
// Set position type.
getPositionType[position] = positionType;
// Now that position type is set up, check that asset of position is supported for pricing operations.
ERC20 positionAsset = _assetOf(position);
if (!PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT)).isSupported(positionAsset))
revert Cellar__PositionPricingNotSetUp(address(positionAsset));
emit TrustChanged(position, true);
}
// ============================================ WITHDRAW CONFIG ============================================
/**
* @notice Emitted when withdraw type configuration is changed.
* @param oldType previous withdraw type
* @param newType new withdraw type
*/
event WithdrawTypeChanged(WithdrawType oldType, WithdrawType newType);
/**
* @notice The withdraw type to use for the cellar.
* @param ORDERLY use `positions` in specify the order in which assets are withdrawn (eg.
* `positions[0]` is withdrawn from first); least impactful positions (position
* that will have its core positions impacted the least by having funds removed)
* should be withdrawn from first and most impactful position should be last
* @param PROPORTIONAL pull assets from each position proportionally when withdrawing, used if
* trying to maintain a specific ratio
*/
enum WithdrawType {
ORDERLY,
PROPORTIONAL
}
/**
* @notice The withdraw type to used by the cellar.
*/
WithdrawType public withdrawType;
/**
* @notice Set the withdraw type used by the cellar.
* @param newWithdrawType value of the new withdraw type to use
*/
function setWithdrawType(WithdrawType newWithdrawType) external onlyOwner {
emit WithdrawTypeChanged(withdrawType, newWithdrawType);
withdrawType = newWithdrawType;
}
// ============================================ HOLDINGS CONFIG ============================================
/**
* @notice Emitted when the holdings position is changed.
* @param oldPosition address of the old holdings position
* @param newPosition address of the new holdings position
*/
event HoldingPositionChanged(address indexed oldPosition, address indexed newPosition);
/**
* @notice The "default" position which uses the same asset as the cellar. It is the position
* deposited assets will automatically go into (perhaps while waiting to be rebalanced
* to other positions) and commonly the first position withdrawn assets will be pulled
* from if using orderly withdraws.
* @dev MUST accept the same asset as the cellar's `asset`. MUST be a position present in
* `positions`. Should be a static (eg. just holding) or lossless (eg. lending on Aave)
* position. Should not be expensive to move assets in or out of as this will occur
* frequently. It is highly recommended to choose a "simple" holding position.
*/
address public holdingPosition;
/**
* @notice Set the holding position used by the cellar.
* @param newHoldingPosition address of the new holding position to use
*/
function setHoldingPosition(address newHoldingPosition) external onlyOwner {
if (!isPositionUsed[newHoldingPosition]) revert Cellar__InvalidPosition(newHoldingPosition);
ERC20 holdingPositionAsset = _assetOf(newHoldingPosition);
if (holdingPositionAsset != asset) revert Cellar__AssetMismatch(address(holdingPositionAsset), address(asset));
emit HoldingPositionChanged(holdingPosition, newHoldingPosition);
holdingPosition = newHoldingPosition;
}
// ============================================ ACCRUAL STORAGE ============================================
/**
* @notice Timestamp of when the last accrual occurred.
* @dev Used for determining the amount of platform fees that can be taken during an accrual period.
*/
uint64 public lastAccrual;
// =============================================== 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 performance fees is changed.
* @param oldPerformanceFee value performance fee was changed from
* @param newPerformanceFee value performance fee was changed to
*/
event PerformanceFeeChanged(uint64 oldPerformanceFee, uint64 newPerformanceFee);
/**
* @notice Emitted when fees distributor is changed.
* @param oldFeesDistributor address of fee distributor was changed from
* @param newFeesDistributor address of fee distributor was changed to
*/
event FeesDistributorChanged(bytes32 oldFeesDistributor, bytes32 newFeesDistributor);
/**
* @notice Emitted when strategist performance fee cut is changed.
* @param oldPerformanceCut value strategist performance fee cut was changed from
* @param newPerformanceCut value strategist performance fee cut was changed to
*/
event StrategistPerformanceCutChanged(uint64 oldPerformanceCut, uint64 newPerformanceCut);
/**
* @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 use an invalid cosmos address.
*/
error Cellar__InvalidCosmosAddress();
/**
* @notice Attempted to change strategist fee cut with invalid value.
*/
error Cellar__InvalidFeeCut();
/**
* @notice Attempted to change performance/platform fee with invalid value.
*/
error Cellar__InvalidFee();
/**
* @notice Data related to fees.
* @param highWatermark Stores the share price to be used as a High Watermark to calculate performance fees.
* @param strategistPerformanceCut Determines how much performance fees go to strategist.
* This should be a value out of 1e18 (ie. 1e18 represents 100%, 0 represents 0%).
* @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 performanceFee 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 feesDistributor Cosmos address of module that distributes fees, specified as a hex value.
* The Gravity contract expects a 32-byte value formatted in a specific way.
* @param strategistPayoutAddress Address to send the strategists fee shares.
*/
struct FeeData {
uint256 highWatermark;
uint64 strategistPerformanceCut;
uint64 strategistPlatformCut;
uint64 platformFee;
uint64 performanceFee;
bytes32 feesDistributor;
address strategistPayoutAddress;
}
/**
* @notice Stores all fee data for cellar.
*/
FeeData public feeData =
FeeData({
highWatermark: 0,
strategistPerformanceCut: 0.75e18,
strategistPlatformCut: 0.75e18,
platformFee: 0.02e18,
performanceFee: 0.1e18,
feesDistributor: hex"000000000000000000000000b813554b423266bbd4c16c32fa383394868c1f55", // 20 bytes, so need 12 bytes of zero
strategistPayoutAddress: address(0)
});
uint64 public constant MAX_PERFORMANCE_FEE = 0.5e18;
uint64 public constant MAX_PLATFORM_FEE = 0.2e18;
uint64 public constant MAX_FEE_CUT = 1e18;
/**
* @notice Set the percentage of platform fees accrued over a year.
* @param newPlatformFee value out of 1e18 that represents new platform fee percentage
*/
function setPlatformFee(uint64 newPlatformFee) external onlyOwner {
if (newPlatformFee > MAX_PLATFORM_FEE) revert Cellar__InvalidFee();
emit PlatformFeeChanged(feeData.platformFee, newPlatformFee);
feeData.platformFee = newPlatformFee;
}
/**
* @notice Set the percentage of performance fees accrued from yield.
* @param newPerformanceFee value out of 1e18 that represents new performance fee percentage
*/
function setPerformanceFee(uint64 newPerformanceFee) external onlyOwner {
if (newPerformanceFee > MAX_PERFORMANCE_FEE) revert Cellar__InvalidFee();
emit PerformanceFeeChanged(feeData.performanceFee, newPerformanceFee);
feeData.performanceFee = newPerformanceFee;
}
/**
* @notice Set the address of the fee distributor on the Sommelier chain.
* @dev IMPORTANT: Ensure that the address is formatted in the specific way that the Gravity contract
* expects it to be.
* @param newFeesDistributor formatted address of the new fee distributor module
*/
function setFeesDistributor(bytes32 newFeesDistributor) external onlyOwner {
if (uint256(newFeesDistributor) > type(uint160).max) revert Cellar__InvalidCosmosAddress();
emit FeesDistributorChanged(feeData.feesDistributor, newFeesDistributor);
feeData.feesDistributor = newFeesDistributor;
}
/**
* @notice Sets the Strategists cut of performance fees
* @param cut the performance cut for the strategist
*/
function setStrategistPerformanceCut(uint64 cut) external onlyOwner {
if (cut > MAX_FEE_CUT) revert Cellar__InvalidFeeCut();
emit StrategistPerformanceCutChanged(feeData.strategistPerformanceCut, cut);
feeData.strategistPerformanceCut = cut;
}
/**
* @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;
}
// ============================================= LIMITS CONFIG =============================================
/**
* @notice Emitted when the liquidity limit is changed.
* @param oldLimit amount the limit was changed from
* @param newLimit amount the limit was changed to
*/
event LiquidityLimitChanged(uint256 oldLimit, uint256 newLimit);
/**
* @notice Emitted when the deposit limit is changed.
* @param oldLimit amount the limit was changed from
* @param newLimit amount the limit was changed to
*/
event DepositLimitChanged(uint256 oldLimit, uint256 newLimit);
/**
* @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 Maximum amount of assets that can be managed by the cellar. Denominated in the same decimals
* as the current asset.
* @dev Set to `type(uint256).max` to have no limit.
*/
uint256 public liquidityLimit = type(uint256).max;
/**
* @notice Maximum amount of assets per wallet. Denominated in the same decimals as the current asset.
* @dev Set to `type(uint256).max` to have no limit.
*/
uint256 public depositLimit = type(uint256).max;
/**
* @notice Set the maximum liquidity that cellar can manage. Uses the same decimals as the current asset.
* @param newLimit amount of assets to set as the new limit
*/
function setLiquidityLimit(uint256 newLimit) external onlyOwner {
emit LiquidityLimitChanged(liquidityLimit, newLimit);
liquidityLimit = newLimit;
}
/**
* @notice Set the per-wallet deposit limit. Uses the same decimals as the current asset.
* @param newLimit amount of assets to set as the new limit
*/
function setDepositLimit(uint256 newLimit) external onlyOwner {
emit DepositLimitChanged(depositLimit, newLimit);
depositLimit = newLimit;
}
// =========================================== EMERGENCY LOGIC ===========================================
/**
* @notice Emitted when cellar emergency state is changed.
* @param isShutdown whether the cellar is shutdown
*/
event ShutdownChanged(bool isShutdown);
/**
* @notice Attempted action was prevented due to contract being shutdown.
*/
error Cellar__ContractShutdown();
/**
* @notice Attempted action was prevented due to contract not being shutdown.
*/
error Cellar__ContractNotShutdown();
/**
* @notice Whether or not the contract is shutdown in case of an emergency.
*/
bool public isShutdown;
/**
* @notice Prevent a function from being called during a shutdown.
*/
modifier whenNotShutdown() {
if (isShutdown) revert Cellar__ContractShutdown();
_;
}
/**
* @notice Shutdown the cellar. Used in an emergency or if the cellar has been deprecated.
* @dev In the case where
*/
function initiateShutdown() external whenNotShutdown onlyOwner {
isShutdown = true;
emit ShutdownChanged(true);
}
/**
* @notice Restart the cellar.
*/
function liftShutdown() external onlyOwner {
if (!isShutdown) revert Cellar__ContractNotShutdown();
isShutdown = false;
emit ShutdownChanged(false);
}
// =========================================== CONSTRUCTOR ===========================================
/**
* @notice Address of the platform's registry contract. Used to get the latest address of modules.
*/
Registry public immutable 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 _positions addresses of the positions to initialize the cellar with
* @param _positionTypes types of each positions used
* @param _holdingPosition address of the position to use as the holding position
* @param _withdrawType withdraw type to use for the cellar
* @param _name name of this cellar's share token
* @param _name symbol of this cellar's share token
* @param _strategistPayout The address to send the strategists fee shares.
*/
constructor(
Registry _registry,
ERC20 _asset,
address[] memory _positions,
PositionType[] memory _positionTypes,
address _holdingPosition,
WithdrawType _withdrawType,
string memory _name,
string memory _symbol,
address _strategistPayout
) ERC4626(_asset, _name, _symbol) Owned(_registry.getAddress(0)) {
registry = _registry;
// Initialize positions.
positions = _positions;
ERC20 positionAsset;
for (uint256 i; i < _positions.length; i++) {
address position = _positions[i];
if (isPositionUsed[position]) revert Cellar__PositionAlreadyUsed(position);
isTrusted[position] = true;
isPositionUsed[position] = true;
getPositionType[position] = _positionTypes[i];
positionAsset = _assetOf(position);
if (!PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT)).isSupported(positionAsset))
revert Cellar__PositionPricingNotSetUp(address(positionAsset));
}
// Initialize holding position.
if (!isPositionUsed[_holdingPosition]) revert Cellar__InvalidPosition(_holdingPosition);
ERC20 holdingPositionAsset = _assetOf(_holdingPosition);
if (holdingPositionAsset != _asset)
revert Cellar__AssetMismatch(address(holdingPositionAsset), address(_asset));
holdingPosition = _holdingPosition;
// Initialize withdraw type.
withdrawType = _withdrawType;
// 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.
lastAccrual = uint64(block.timestamp);
feeData.strategistPayoutAddress = _strategistPayout;
}
// =========================================== CORE LOGIC ===========================================
/**
* @notice Emitted when withdraws are made from a position.
* @param position the position assets were withdrawn from
* @param amount the amount of assets withdrawn
*/
event PulledFromPosition(address indexed position, uint256 amount);
/**
* @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 blockSharesAreUnlocked the block number when caller can transfer/redeem shares
* @param currentBlock the current block number.
*/
error Cellar__SharesAreLocked(uint256 blockSharesAreUnlocked, uint256 currentBlock);
/**
* @notice Attempted deposit on behalf of a user without being approved.
*/
error Cellar__NotApprovedToDepositOnBehalf(address depositor);
/**
* @notice Shares must be locked for atleaset 8 blocks after minting.
*/
uint256 public constant MINIMUM_SHARE_LOCK_PERIOD = 8;
/**
* @notice Shares can be locked for at most 7200 blocks after minting.
*/
uint256 public constant MAXIMUM_SHARE_LOCK_PERIOD = 7200;
/**
* @notice After deposits users must wait `shareLockPeriod` blocks before being able to transfer or withdraw their shares.
*/
uint256 public shareLockPeriod = MAXIMUM_SHARE_LOCK_PERIOD;
/**
* @notice mapping that stores every users last block they minted shares.
*/
mapping(address => uint256) public userShareLockStartBlock;
/**
* @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 blocks have passed to unlock shares.
* @param owner the address of the user to check
*/
function _checkIfSharesLocked(address owner) internal view {
uint256 lockBlock = userShareLockStartBlock[owner];
if (lockBlock != 0) {
uint256 blockSharesAreUnlocked = lockBlock + shareLockPeriod;
if (blockSharesAreUnlocked > block.number)
revert Cellar__SharesAreLocked(blockSharesAreUnlocked, block.number);
}
}
/**
* @notice modifies before transfer hook to check that shares are not locked
* @param from the address transferring shares
*/
function _beforeTokenTransfer(
address from,
address,
uint256
) internal view override {
_checkIfSharesLocked(from);
}
/**
* @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 override whenNotShutdown {
if (msg.sender != receiver) {
if (!registry.approvedForDepositOnBehalf(msg.sender))
revert Cellar__NotApprovedToDepositOnBehalf(msg.sender);
}
uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) revert Cellar__DepositRestricted(assets, maxAssets);
feeData.highWatermark += assets;
}
/**
* @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);
userShareLockStartBlock[receiver] = block.number;
}
/**
* @notice called at the beginning of withdraw.
* @param assets amount of assets withdrawn by user.
*/
function beforeWithdraw(
uint256 assets,
uint256,
address,
address owner
) internal override {
// Make sure users shares are not locked.
_checkIfSharesLocked(owner);
// Need to check if assets is greater than the high watermark
// because if the performanceFee is set to zero, and all cellar shares are redeemed,
// if the cellar has earned any yield, assets will be greater than the high watermark.
// Becuase the high watermark is only updated when performance fees are minted.
uint256 highWatermark = feeData.highWatermark;
feeData.highWatermark = assets > highWatermark ? 0 : highWatermark - assets;
}
/**
* @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) {
uint256 _totalAssets = totalAssets();
_takePerformanceFees(_totalAssets);
// Check for rounding error since we round down in previewDeposit.
if ((shares = _convertToShares(assets, _totalAssets)) == 0) revert Cellar__ZeroShares();
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 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) {
uint256 _totalAssets = totalAssets();
_takePerformanceFees(_totalAssets);
// previewMintRoundsUp, but iniital mint could return zero assets, so check for rounding error.
if ((assets = _previewMint(shares, _totalAssets)) == 0) revert Cellar__ZeroAssets(); // 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);
}
/**
* @notice helper function that checks if msg.sender has the allowance to spend owner's shares.
* @dev reverts if msg.sender != owner, and msg.sender does not have enough allowance.
*/
function _checkAllowance(address owner, uint256 shares) internal {
if (msg.sender != owner) {
_spendAllowance(owner, msg.sender, shares);
}
}
/**
* @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) {
// Get data efficiently.
(
uint256 _totalAssets, // Store totalHoldings and pass into _withdrawInOrder if no stack errors.
address[] memory _positions,
ERC20[] memory positionAssets,
uint256[] memory positionBalances,
uint256[] memory withdrawableBalances
) = _getData();
_takePerformanceFees(_totalAssets);
// No need to check for rounding error, `previewWithdraw` rounds up.
shares = _previewWithdraw(assets, _totalAssets);
beforeWithdraw(assets, shares, receiver, owner);
_checkAllowance(owner, shares);
uint256 totalShares = totalSupply();
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
withdrawType == WithdrawType.ORDERLY
? _withdrawInOrder(assets, receiver, _positions, positionAssets, positionBalances, withdrawableBalances)
: _withdrawInProportion(shares, totalShares, receiver, _positions, positionBalances, withdrawableBalances);
afterWithdraw(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) {
// Get data efficiently.
(
uint256 _totalAssets, // Store totalHoldings and pass into _withdrawInOrder if no stack errors.
address[] memory _positions,
ERC20[] memory positionAssets,
uint256[] memory positionBalances,
uint256[] memory withdrawableBalances
) = _getData();
_takePerformanceFees(_totalAssets);
_checkAllowance(owner, shares);
// Check for rounding error since we round down in previewRedeem.
if ((assets = _convertToAssets(shares, _totalAssets)) == 0) revert Cellar__ZeroAssets();
beforeWithdraw(assets, shares, receiver, owner);
uint256 totalShares = totalSupply();
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
withdrawType == WithdrawType.ORDERLY
? _withdrawInOrder(assets, receiver, _positions, positionAssets, positionBalances, withdrawableBalances)
: _withdrawInProportion(shares, totalShares, receiver, _positions, positionBalances, withdrawableBalances);
afterWithdraw(assets, shares, receiver, owner);
}
/**
* @dev Withdraw from positions in the order defined by `positions`. Used if the withdraw type
* is `ORDERLY`.
* @param assets the amount of assets to withdraw from cellar
* @param receiver the address to sent withdrawn assets to
* @param _positions positions to withdraw from
* @param positionAssets underlying asset for each position
* @param positionBalances underlying balances for each position
*/
function _withdrawInOrder(
uint256 assets,
address receiver,
address[] memory _positions,
ERC20[] memory positionAssets,
uint256[] memory positionBalances,
uint256[] memory withdrawableBalances
) internal {
// Get the price router.
PriceRouter priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
for (uint256 i; i < _positions.length; i++) {
// Move on to next position if this one is empty.
if (positionBalances[i] == 0) continue;
uint256 onePositionAsset = 10**positionAssets[i].decimals();
uint256 exchangeRate = priceRouter.getExchangeRate(positionAssets[i], asset);
// Denominate withdrawable position balance in cellar's asset.
uint256 totalWithdrawableBalanceInAssets = withdrawableBalances[i].mulDivDown(
exchangeRate,
onePositionAsset
);
// We want to pull as much as we can from this position, but no more than needed.
uint256 amount;
if (totalWithdrawableBalanceInAssets > assets) {
amount = assets.mulDivDown(onePositionAsset, exchangeRate);
assets = 0;
} else {
amount = withdrawableBalances[i];
assets = assets - totalWithdrawableBalanceInAssets;
}
// Withdraw from position.
_withdrawFrom(_positions[i], amount, receiver);
emit PulledFromPosition(_positions[i], amount);
// 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);
}
/**
* @dev Withdraw from each position proportional to that of shares redeemed. Used if the
* withdraw type is `PROPORTIONAL`.
* @dev It is possible that the `amount` calculated to withdraw is zero. This is only a problem
* for a low percision ERC20, which we have no plans to support.
* @param shares the user is burning to withdraw
* @param totalShares the total amount of oustanding shares
* @param receiver the address to sent withdrawn assets to
* @param _positions positions to withdraw from
* @param positionBalances underlying balances for each position
*/
function _withdrawInProportion(
uint256 shares,
uint256 totalShares,
address receiver,
address[] memory _positions,
uint256[] memory positionBalances,
uint256[] memory withdrawableBalances
) internal {
// Withdraw assets from positions in proportion to shares redeemed.
for (uint256 i; i < _positions.length; i++) {
address position = _positions[i];
uint256 positionBalance = positionBalances[i];
// Move on to next position if this one is empty.
if (positionBalance == 0) continue;
// Get the amount of assets to withdraw from this position based on proportion to shares redeemed.
uint256 amount = positionBalance.mulDivDown(shares, totalShares);
// If straetgist locks the enirety of a positions funds, then all withdraw calls revert.
// If this happens, goverance should vote out malicious strategist, then change withdraw type to in oder, and move bad position to back of queue.
if (amount > withdrawableBalances[i]) revert Cellar__IlliquidWithdraw(position);
// Withdraw from position to receiver.
_withdrawFrom(position, amount, receiver);
emit PulledFromPosition(position, amount);
}
}
// ========================================= ACCOUNTING LOGIC =========================================
/**
* @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.
*/
function totalAssets() public view override returns (uint256 assets) {
uint256 numOfPositions = positions.length;
ERC20[] memory positionAssets = new ERC20[](numOfPositions);
uint256[] memory balances = new uint256[](numOfPositions);
for (uint256 i; i < numOfPositions; i++) {
address position = positions[i];
positionAssets[i] = _assetOf(position);
balances[i] = _balanceOf(position);
}
PriceRouter priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
assets = priceRouter.getValues(positionAssets, balances, asset);
}
/**
* @notice The total amount of assets in the cellar.
* @dev Excludes locked yield that hasn't been distributed.
*/
function totalAssetsWithdrawable() public view returns (uint256 assets) {
uint256 numOfPositions = positions.length;
ERC20[] memory positionAssets = new ERC20[](numOfPositions);
uint256[] memory balances = new uint256[](numOfPositions);
for (uint256 i; i < numOfPositions; i++) {
address position = positions[i];
positionAssets[i] = _assetOf(position);
balances[i] = _withdrawableFrom(position);
}
PriceRouter priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
assets = priceRouter.getValues(positionAssets, balances, asset);
}
/**
* @notice The amount of assets that the cellar would exchange for the amount of shares provided.
* @notice is NOT inclusive of performance fees.
* @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();
uint256 feeInAssets = _previewPerformanceFees(_totalAssets);
assets = _previewMint(shares, _totalAssets - feeInAssets);
}
/**
* @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();
uint256 feeInAssets = _previewPerformanceFees(_totalAssets);
shares = _previewWithdraw(assets, _totalAssets - feeInAssets);
}
/**
* @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();
uint256 feeInAssets = _previewPerformanceFees(_totalAssets);
shares = _convertToShares(assets, _totalAssets - feeInAssets);
}
/**
* @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();
uint256 feeInAssets = _previewPerformanceFees(_totalAssets);
assets = _convertToAssets(shares, _totalAssets - feeInAssets);
}
/**
* @notice Finds the max amount of value an `owner` can remove from the cellar.
* @param owner address of the user to find max value.
* @param inShares if false, then returns value in terms of assets
* if true then returns value in terms of shares
*/
function _findMax(address owner, bool inShares) internal view returns (uint256 maxOut) {
// Check if owner shares are locked, return 0 if so.
uint256 lockBlock = userShareLockStartBlock[owner];
if (lockBlock != 0) {
uint256 blockSharesAreUnlocked = lockBlock + shareLockPeriod;
if (blockSharesAreUnlocked > block.number) return 0;
}
// Get amount of assets to withdraw with fees accounted for.
uint256 _totalAssets = totalAssets();
uint256 feeInAssets = _previewPerformanceFees(_totalAssets);
uint256 assets = _convertToAssets(balanceOf(owner), _totalAssets - feeInAssets);
if (withdrawType == WithdrawType.ORDERLY) {
uint256 withdrawable = totalAssetsWithdrawable();
maxOut = assets <= withdrawable ? assets : withdrawable;
} else {
(, , , uint256[] memory positionBalances, uint256[] memory withdrawableBalances) = _getData();
uint256 totalShares = totalSupply();
uint256 shares = balanceOf(owner);
uint256 smallestPercentWithdrawable = 1e18;
for (uint256 i = 0; i < withdrawableBalances.length; i++) {
if (positionBalances[i] == 0) continue;
if (withdrawableBalances[i] == 0) return 0;
uint256 percentWithdrawable = withdrawableBalances[i].mulDivDown(1e18, positionBalances[i]);
if (percentWithdrawable < smallestPercentWithdrawable)
smallestPercentWithdrawable = percentWithdrawable;
}
uint256 userOwnershipPercent = shares.mulDivDown(1e18, totalShares);
maxOut = userOwnershipPercent <= smallestPercentWithdrawable
? assets
: (_totalAssets - feeInAssets).mulDivDown(smallestPercentWithdrawable, 1e18);
}
if (inShares) maxOut = _convertToShares(maxOut, _totalAssets - feeInAssets);
// 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) {
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) {
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);
}
/**
* @dev Used to efficiently get and store accounting information to avoid having to expensively
* recompute it.
*/
function _getData()
internal
view
returns (
uint256 _totalAssets,
address[] memory _positions,
ERC20[] memory positionAssets,
uint256[] memory positionBalances,
uint256[] memory withdrawableBalances
)
{
uint256 len = positions.length;
_positions = new address[](len);
positionAssets = new ERC20[](len);
positionBalances = new uint256[](len);
positionBalances = new uint256[](len);
withdrawableBalances = new uint256[](len);
for (uint256 i; i < len; i++) {
address position = positions[i];
_positions[i] = position;
positionAssets[i] = _assetOf(position);
positionBalances[i] = _balanceOf(position);
withdrawableBalances[i] = _withdrawableFrom(position);
}
PriceRouter priceRouter = PriceRouter(registry.getAddress(PRICE_ROUTER_REGISTRY_SLOT));
_totalAssets = priceRouter.getValues(positionAssets, positionBalances, asset);
}
// =========================================== POSITION LOGIC ===========================================
/**
* @notice Emitted on rebalancing positions.
* @param fromPosition the address of the position rebalanced from
* @param toPosition the address of the position rebalanced to
* @param assetsFrom the amount of assets withdrawn from the position rebalanced from
* @param assetsTo the amount of assets desposited to the position rebalanced to
*/
event Rebalance(address indexed fromPosition, address indexed toPosition, uint256 assetsFrom, uint256 assetsTo);
/**
* @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);
uint64 public constant MAX_REBALANCE_DEVIATION = 0.1e18;
/**
* @notice The percent the total assets of a cellar may deviate during a rebalance call.
*/
uint256 public allowedRebalanceDeviation = 0.003e18; // Currently set to 0.3%
/**
* @notice Allows governance to change this cellars rebalance deviation.
* @param newDeviation the new reabalance 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);
}
/**
* @notice Move assets between positions. To move assets from/to this cellar's holdings, specify
* the address of this cellar as the `fromPosition`/`toPosition`.
* @param fromPosition address of the position to move assets from
* @param toPosition address of the position to move assets to
* @param assetsFrom amount of assets to move from the from position
*/
function rebalance(
address fromPosition,
address toPosition,
uint256 assetsFrom,
SwapRouter.Exchange exchange,
bytes calldata params
) external onlyOwner whenNotShutdown nonReentrant returns (uint256 assetsTo) {
// Check that position being rebalanced to is currently being used.
if (!isPositionUsed[toPosition]) revert Cellar__InvalidPosition(address(toPosition));
// Before making any external calls save the current `totalAssets` and `totalSupply`.
uint256 assets = totalAssets();
uint256 totalShares = totalSupply();
// Withdraw from position.
_withdrawFrom(fromPosition, assetsFrom, address(this));
// Swap to the asset of the other position if necessary.
ERC20 fromAsset = _assetOf(fromPosition);
ERC20 toAsset = _assetOf(toPosition);
assetsTo = fromAsset != toAsset
? _swap(fromAsset, toAsset, assetsFrom, exchange, params, address(this))
: assetsFrom;
// Deposit into position.
_depositTo(toPosition, assetsTo);
// After making every external call, check that the totalAssets haas not deviated significantly, and that totalShares is the same.
uint256 minimumAllowedAssets = assets.mulDivUp((1e18 - allowedRebalanceDeviation), 1e18);
uint256 maximumAllowedAssets = assets.mulDivDown((1e18 + allowedRebalanceDeviation), 1e18);
assets = totalAssets();
if (assets > maximumAllowedAssets || assets < minimumAllowedAssets)
revert Cellar__TotalAssetDeviatedOutsideRange(assets, minimumAllowedAssets, maximumAllowedAssets);
if (totalShares != totalSupply()) revert Cellar__TotalSharesMustRemainConstant(totalSupply(), totalShares);
emit Rebalance(fromPosition, toPosition, assetsFrom, assetsTo);
}
// ============================================ LIMITS LOGIC ============================================
/**
* @notice Total amount of assets that can be deposited for a user.
* @dev This function does not take into account performance fees.
* Performance fees would reduce `receiver`s `ownedAssets`,
* making the `assets` value returned lower than actual
* @dev EIP4626 states maxDeposit must not revert, but it is possible for `totalAssets` to revert
* so it does NOT conform to ERC4626 standards.
* @param receiver address of account that would receive the shares
* @return assets maximum amount of assets that can be deposited
*/
function maxDeposit(address receiver) public view override returns (uint256 assets) {
if (isShutdown) return 0;
uint256 asssetDepositLimit = depositLimit;
uint256 asssetLiquidityLimit = liquidityLimit;
if (asssetDepositLimit == type(uint256).max && asssetLiquidityLimit == type(uint256).max)
return type(uint256).max;
// Get data efficiently.
uint256 _totalAssets = totalAssets();
uint256 ownedAssets = _convertToAssets(balanceOf(receiver), _totalAssets);
uint256 leftUntilDepositLimit = asssetDepositLimit.subMinZero(ownedAssets);
uint256 leftUntilLiquidityLimit = asssetLiquidityLimit.subMinZero(_totalAssets);
// Only return the more relevant of the two.
assets = Math.min(leftUntilDepositLimit, leftUntilLiquidityLimit);
}
/**
* @notice Total amount of shares that can be minted for a user.
* @dev This function does not take into account performance fees.
* Performance fees would reduce `receiver`s `ownedAssets`,
* making the `shares` value returned lower than actual
* @dev EIP4626 states maxMint must not revert, but it is possible for `totalAssets` to revert
* so it does NOT conform to ERC4626 standards.
* @param receiver address of account that would receive the shares
* @return shares maximum amount of shares that can be minted
*/
function maxMint(address receiver) public view override returns (uint256) {
uint256 amount = maxDeposit(receiver);
return amount == type(uint256).max ? amount : convertToShares(amount);
}
// ========================================= FEES LOGIC =========================================
/**
* @notice Emitted when High Watermark is reset.
* @param newHighWatermark new high watermark
*/
event HighWatermarkReset(uint256 newHighWatermark);
/**
* @notice Attempted to send fee shares to strategist payout address, when address is not set.
*/
error Cellar__PayoutNotSet();
/**
* @notice Resets High Watermark to equal current total assets.
* @notice This function can be abused by Strategists, so it should only be callable by governance.
*/
function resetHighWatermark() external onlyOwner {
uint256 _totalAssets = totalAssets();
feeData.highWatermark = _totalAssets;
emit HighWatermarkReset(_totalAssets);
}
/**
* @notice Calculates how many assets Strategist would earn performance fees
* @param _totalAssets uint256 value of the total assets in the cellar
* @return feeInAssets amount of assets to take as fees
*/
function _previewPerformanceFees(uint256 _totalAssets) internal view returns (uint256 feeInAssets) {
uint64 performanceFee = feeData.performanceFee;
if (performanceFee == 0 || _totalAssets == 0) return 0;
uint256 highWatermark = feeData.highWatermark;
if (_totalAssets > highWatermark) {
uint256 yield = _totalAssets - highWatermark;
feeInAssets = yield.mulWadDown(performanceFee);
}
}
/**
* @notice Mints cellar performance fee shares if current share price is above high watermark
* @dev If performance fees are minted, the resulting HWM will be greater than the current share price
* since performance fees dilute share value.
* @param _totalAssets uint256 value of the total assets in the cellar
*/
function _takePerformanceFees(uint256 _totalAssets) internal {
uint256 feeInAssets = _previewPerformanceFees(_totalAssets);
if (feeInAssets > 0) {
uint256 platformFeesInShares = _convertToFees(_convertToShares(feeInAssets, _totalAssets));
if (platformFeesInShares > 0) {
feeData.highWatermark = _totalAssets;
_mint(address(this), platformFeesInShares);
}
}
}
/**
* @dev Calculate the amount of fees to mint such that value of fees after minting is not diluted.
*/
function _convertToFees(uint256 feesInShares) internal view returns (uint256 fees) {
// Saves an SLOAD.
uint256 totalShares = totalSupply();
// Get the amount of fees to mint. Without this, the value of fees minted would be slightly
// diluted because total shares increased while total assets did not. This counteracts that.
if (totalShares > feesInShares) {
// Denominator is greater than zero
uint256 denominator = totalShares - feesInShares;
fees = feesInShares.mulDivUp(totalShares, denominator);
}
// If denominator is less than or equal to zero, `fees` should be zero.
}
/**
* @notice Emitted when platform fees are send to the Sommelier chain.
* @param feesInSharesRedeemed amount of fees redeemed for assets to send
* @param feesInAssetsSent amount of assets fees were redeemed for that were sent
*/
event SendFees(uint256 feesInSharesRedeemed, uint256 feesInAssetsSent);
/**
* @notice Transfer accrued fees to the Sommelier chain to distribute.
* @dev Fees are accrued as shares and redeemed upon transfer.
* @dev assumes cellar's accounting asset is able to be transferred and sent to Cosmos
*/
function sendFees() external nonReentrant {
address strategistPayoutAddress = feeData.strategistPayoutAddress;
if (strategistPayoutAddress == address(0)) revert Cellar__PayoutNotSet();
uint256 _totalAssets = totalAssets();
// Since this action mints shares, calculate outstanding performance fees due.
_takePerformanceFees(_totalAssets);
uint256 totalFees = balanceOf(address(this));
uint256 strategistFeeSharesDue = totalFees.mulWadDown(feeData.strategistPerformanceCut);
// Calculate platform fees earned.
uint256 elapsedTime = block.timestamp - lastAccrual;
uint256 platformFeeInAssets = (_totalAssets * elapsedTime * feeData.platformFee) / 1e18 / 365 days;
uint256 platformFees = _convertToFees(_convertToShares(platformFeeInAssets, _totalAssets));
_mint(address(this), platformFees);
totalFees += platformFees;
strategistFeeSharesDue += platformFees.mulWadDown(feeData.strategistPlatformCut);
if (strategistFeeSharesDue > 0) {
//transfer shares to strategist
_transfer(address(this), strategistPayoutAddress, strategistFeeSharesDue);
totalFees -= strategistFeeSharesDue;
}
lastAccrual = uint32(block.timestamp);
// Redeem our fee shares for assets to send to the fee distributor module.
uint256 assets = _convertToAssets(totalFees, _totalAssets);
if (assets > 0) {
// Without this, assets paid out as fees would be counted as a loss.
feeData.highWatermark -= assets;
_burn(address(this), totalFees);
// Transfer assets to a fee distributor on the Sommelier chain.
IGravity gravityBridge = IGravity(registry.getAddress(0));
asset.safeApprove(address(gravityBridge), assets);
gravityBridge.sendToCosmos(address(asset), feeData.feesDistributor, assets);
}
emit SendFees(totalFees, assets);
}
// ========================================== HELPER FUNCTIONS ==========================================
/**
* @dev Deposit into a position according to its position type and update related state.
* @param position address to deposit funds into
* @param assets the amount of assets to deposit into the position
*/
function _depositTo(address position, uint256 assets) internal {
PositionType positionType = getPositionType[position];
// Deposit into position.
if (positionType == PositionType.ERC4626 || positionType == PositionType.Cellar) {
ERC4626(position).asset().safeApprove(position, assets);
ERC4626(position).deposit(assets, address(this));
}
}
/**
* @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(
address position,
uint256 assets,
address receiver
) internal {
PositionType positionType = getPositionType[position];
// Withdraw from position.
if (positionType == PositionType.ERC4626 || positionType == PositionType.Cellar) {
ERC4626(position).withdraw(assets, receiver, address(this));
} else {
if (receiver != address(this)) ERC20(position).safeTransfer(receiver, assets);
}
}
/**
* @dev Get the withdrawable balance of a position according to its position type.
* @param position position to get the withdrawable balance of
*/
function _withdrawableFrom(address position) internal view returns (uint256) {
PositionType positionType = getPositionType[position];
if (positionType == PositionType.ERC4626 || positionType == PositionType.Cellar) {
return ERC4626(position).maxWithdraw(address(this));
} else {
return ERC20(position).balanceOf(address(this));
}
}
/**
* @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(address position) internal view returns (uint256) {
PositionType positionType = getPositionType[position];
if (positionType == PositionType.ERC4626 || positionType == PositionType.Cellar) {
return ERC4626(position).previewRedeem(ERC4626(position).balanceOf(address(this)));
} else {
return ERC20(position).balanceOf(address(this));
}
}
/**
* @dev Get the asset of a position according to its position type.
* @param position to get the asset of
*/
function _assetOf(address position) internal view returns (ERC20) {
PositionType positionType = getPositionType[position];
if (positionType == PositionType.ERC4626 || positionType == PositionType.Cellar) {
return ERC4626(position).asset();
} else {
return ERC20(position);
}
}
/**
* @notice Attempted to swap with bad parameters.
*/
error Cellar__WrongSwapParams();
/**
* @dev Perform a swap using the swap router and check that it behaves as expected.
* @param assetIn the asset to sell
* @param amountIn the amount of `assetIn` to sell
* @param exchange the exchange to sell `assetIn` on
* @param params Abi encoded swap parameters dependent on the `exchange` selected.
* Refer to SwapRouter.sol for `params` makeup
* @param receiver the address to send the swapped assets to
*/
function _swap(
ERC20 assetIn,
ERC20 assetOut,
uint256 amountIn,
SwapRouter.Exchange exchange,
bytes calldata params,
address receiver
) internal returns (uint256 amountOut) {
// Store the expected amount of the asset in that we expect to have after the swap.
uint256 expectedAssetsInAfter = assetIn.balanceOf(address(this)) - amountIn;
// Get the address of the latest swap router.
SwapRouter swapRouter = SwapRouter(registry.getAddress(1));
// Approve swap router to swap assets.
assetIn.safeApprove(address(swapRouter), amountIn);
// Perform swap.
amountOut = swapRouter.swap(exchange, params, receiver, assetIn, assetOut);
// Check that the amount of assets swapped is what is expected. Will revert if the `params`
// specified a different amount of assets to swap then `amountIn`.
if (assetIn.balanceOf(address(this)) != expectedAssetsInAfter) revert Cellar__WrongSwapParams();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
pragma solidity ^0.8.0;
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
*/
library Counters {
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
unchecked {
counter._value += 1;
}
}
function decrement(Counter storage counter) internal {
uint256 value = counter._value;
require(value > 0, "Counter: decrement overflow");
unchecked {
counter._value = value - 1;
}
}
function reset(Counter storage counter) internal {
counter._value = 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library Denominations {
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public constant BTC = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;
// Fiat currencies follow https://en.wikipedia.org/wiki/ISO_4217
address public constant USD = address(840);
address public constant GBP = address(826);
address public constant EUR = address(978);
address public constant JPY = address(392);
address public constant KRW = address(410);
address public constant CNY = address(156);
address public constant AUD = address(36);
address public constant CAD = address(124);
address public constant CHF = address(756);
address public constant ARS = address(32);
address public constant PHP = address(608);
address public constant NZD = address(554);
address public constant SGD = address(702);
address public constant NGN = address(566);
address public constant ZAR = address(710);
address public constant RUB = address(643);
address public constant INR = address(356);
address public constant BRL = address(986);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
} else if (error == RecoverError.InvalidSignatureV) {
revert("ECDSA: invalid signature 'v' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
// Check the signature length
// - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else if (signature.length == 64) {
bytes32 r;
bytes32 vs;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40))
}
return tryRecover(hash, r, vs);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
if (v != 27 && v != 28) {
return (address(0), RecoverError.InvalidSignatureV);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless this function is
* overridden;
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amount;
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.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, ERC20Permit {
using SafeERC20 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 immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) ERC20Permit(_name) {
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) {
_spendAllowance(owner, msg.sender, 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) {
_spendAllowance(owner, msg.sender, 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: MIT
pragma solidity ^0.8.0;
pragma abicoder v2;
import "./AggregatorV2V3Interface.sol";
interface FeedRegistryInterface {
struct Phase {
uint16 phaseId;
uint80 startingAggregatorRoundId;
uint80 endingAggregatorRoundId;
}
event FeedProposed(
address indexed asset,
address indexed denomination,
address indexed proposedAggregator,
address currentAggregator,
address sender
);
event FeedConfirmed(
address indexed asset,
address indexed denomination,
address indexed latestAggregator,
address previousAggregator,
uint16 nextPhaseId,
address sender
);
// V3 AggregatorV3Interface
function decimals(address base, address quote) external view returns (uint8);
function description(address base, address quote) external view returns (string memory);
function version(address base, address quote) external view returns (uint256);
function latestRoundData(address base, address quote)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function getRoundData(
address base,
address quote,
uint80 _roundId
)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
// V2 AggregatorInterface
function latestAnswer(address base, address quote) external view returns (int256 answer);
function latestTimestamp(address base, address quote) external view returns (uint256 timestamp);
function latestRound(address base, address quote) external view returns (uint256 roundId);
function getAnswer(
address base,
address quote,
uint256 roundId
) external view returns (int256 answer);
function getTimestamp(
address base,
address quote,
uint256 roundId
) external view returns (uint256 timestamp);
// Registry getters
function getFeed(address base, address quote) external view returns (AggregatorV2V3Interface aggregator);
function getPhaseFeed(
address base,
address quote,
uint16 phaseId
) external view returns (AggregatorV2V3Interface aggregator);
function isFeedEnabled(address aggregator) external view returns (bool);
function getPhase(
address base,
address quote,
uint16 phaseId
) external view returns (Phase memory phase);
// Round helpers
function getRoundFeed(
address base,
address quote,
uint80 roundId
) external view returns (AggregatorV2V3Interface aggregator);
function getPhaseRange(
address base,
address quote,
uint16 phaseId
) external view returns (uint80 startingRoundId, uint80 endingRoundId);
function getPreviousRoundId(
address base,
address quote,
uint80 roundId
) external view returns (uint80 previousRoundId);
function getNextRoundId(
address base,
address quote,
uint80 roundId
) external view returns (uint80 nextRoundId);
// Feed management
function proposeFeed(
address base,
address quote,
address aggregator
) external;
function confirmFeed(
address base,
address quote,
address aggregator
) external;
// Proposed aggregator
function getProposedFeed(address base, address quote)
external
view
returns (AggregatorV2V3Interface proposedAggregator);
function proposedGetRoundData(
address base,
address quote,
uint80 roundId
)
external
view
returns (
uint80 id,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function proposedLatestRoundData(address base, address quote)
external
view
returns (
uint80 id,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
// Phases
function getCurrentPhaseId(address base, address quote) external view returns (uint16 currentPhaseId);
}
// 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);
}
// 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 v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// 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: 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: 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;
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: 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/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: MIT
// OpenZeppelin Contracts v4.4.1 (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 Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
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: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnerUpdated(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 OwnerUpdated(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function setOwner(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnerUpdated(msg.sender, newOwner);
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { FeedRegistryInterface } from "@chainlink/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol";
import { AggregatorV2V3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol";
import { IChainlinkAggregator } from "src/interfaces/external/IChainlinkAggregator.sol";
import { Denominations } from "@chainlink/contracts/src/v0.8/Denominations.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Math } from "src/utils/Math.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 {
using SafeERC20 for ERC20;
using SafeCast for int256;
using Math for uint256;
event AddAsset(address indexed asset);
// =========================================== ASSETS CONFIG ===========================================
/**
* @param minPrice minimum price in USD for the asset before reverting
* @param maxPrice maximum price in USD for the asset before reverting
* @param isPriceRangeInETH if true price range values are given in ETH, if false price range is given in USD
* @param heartbeat maximum allowed time that can pass with no update before price data is considered stale
* @param isSupported whether this asset is supported by the platform or not
*/
struct AssetConfig {
uint256 minPrice;
uint256 maxPrice;
bool isPriceRangeInETH;
uint96 heartbeat;
bool isSupported;
}
/**
* @notice Get the asset data for a given asset.
*/
mapping(ERC20 => AssetConfig) public getAssetConfig;
uint96 public constant DEFAULT_HEART_BEAT = 1 days;
// ======================================= 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 with a certain price range denomination, but actual denomination was different.
* @param expected price range denomination
* @param actual price range denomination
* @dev If an asset has price feeds in USD and ETH, the feed in USD is favored
*/
error PriceRouter__PriceRangeDenominationMisMatch(bool expected, bool actual);
/**
* @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 Add an asset for the price router to support.
* @param asset address of asset to support on the platform
* @param minPrice minimum price in USD with 8 decimals for the asset before reverting,
* set to `0` to use Chainlink's default
* @param maxPrice maximum price in USD with 8 decimals for the asset before reverting,
* set to `0` to use Chainlink's default
* @param heartbeat maximum amount of time that can pass without the price data being updated
* before reverting, set to `0` to use the default of 1 day
*/
function addAsset(
ERC20 asset,
uint256 minPrice,
uint256 maxPrice,
bool rangeInETH,
uint96 heartbeat
) external onlyOwner {
if (address(asset) == address(0)) revert PriceRouter__InvalidAsset(address(asset));
// Use Chainlink to get the min and max of the asset.
ERC20 assetToQuery = _remap(asset);
(uint256 minFromChainklink, uint256 maxFromChainlink, bool isETH) = _getPriceRange(assetToQuery);
// Check if callers expected price range denomination matches actual.
if (rangeInETH != isETH) revert PriceRouter__PriceRangeDenominationMisMatch(rangeInETH, isETH);
// 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.mulWadDown(1.1e18);
uint256 bufferedMaxPrice = maxFromChainlink.mulWadDown(0.9e18);
if (minPrice == 0) {
minPrice = bufferedMinPrice;
} else {
if (minPrice < bufferedMinPrice) revert PriceRouter__InvalidMinPrice(minPrice, bufferedMinPrice);
}
if (maxPrice == 0) {
maxPrice = bufferedMaxPrice;
} else {
if (maxPrice > bufferedMaxPrice) revert PriceRouter__InvalidMaxPrice(maxPrice, bufferedMaxPrice);
}
if (minPrice >= maxPrice) revert PriceRouter__MinPriceGreaterThanMaxPrice(minPrice, maxPrice);
getAssetConfig[asset] = AssetConfig({
minPrice: minPrice,
maxPrice: maxPrice,
isPriceRangeInETH: isETH,
heartbeat: heartbeat != 0 ? heartbeat : DEFAULT_HEART_BEAT,
isSupported: true
});
emit AddAsset(address(asset));
}
function isSupported(ERC20 asset) external view returns (bool) {
return getAssetConfig[asset].isSupported;
}
// ======================================= PRICING OPERATIONS =======================================
/**
* @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) {
value = amount.mulDivDown(getExchangeRate(baseAsset, quoteAsset), 10**baseAsset.decimals());
}
/**
* @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[] memory baseAssets,
uint256[] memory amounts,
ERC20 quoteAsset
) external view returns (uint256 value) {
uint256 numOfAssets = baseAssets.length;
if (numOfAssets != amounts.length) revert PriceRouter__LengthMismatch();
uint8 quoteAssetDecimals = quoteAsset.decimals();
for (uint256 i; i < numOfAssets; i++) {
ERC20 baseAsset = baseAssets[i];
value += amounts[i].mulDivDown(
_getExchangeRate(baseAsset, quoteAsset, quoteAssetDecimals),
10**baseAsset.decimals()
);
}
}
/**
* @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) {
exchangeRate = _getExchangeRate(baseAsset, quoteAsset, quoteAsset.decimals());
}
/**
* @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();
uint256 numOfAssets = baseAssets.length;
exchangeRates = new uint256[](numOfAssets);
for (uint256 i; i < numOfAssets; i++)
exchangeRates[i] = _getExchangeRate(baseAssets[i], quoteAsset, quoteAssetDecimals);
}
/**
* @notice Get the minimum and maximum valid price for an asset.
* @param asset address of the asset to get the price range of
* @return min minimum valid price for the asset
* @return max maximum valid price for the asset
*/
function getPriceRange(ERC20 asset)
public
view
returns (
uint256 min,
uint256 max,
bool isETH
)
{
AssetConfig memory config = getAssetConfig[asset];
if (!config.isSupported) revert PriceRouter__UnsupportedAsset(address(asset));
(min, max, isETH) = (config.minPrice, config.maxPrice, config.isPriceRangeInETH);
}
/**
* @notice Get the minimum and maximum valid prices for an asset.
* @param _assets addresses of the assets to get the price ranges for
* @return min minimum valid price for each asset
* @return max maximum valid price for each asset
*/
function getPriceRanges(ERC20[] memory _assets)
external
view
returns (
uint256[] memory min,
uint256[] memory max,
bool[] memory isETH
)
{
uint256 numOfAssets = _assets.length;
(min, max, isETH) = (new uint256[](numOfAssets), new uint256[](numOfAssets), new bool[](numOfAssets));
for (uint256 i; i < numOfAssets; i++) (min[i], max[i], isETH[i]) = getPriceRange(_assets[i]);
}
// =========================================== HELPER FUNCTIONS ===========================================
ERC20 private constant WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
ERC20 private constant WBTC = ERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599);
function _remap(ERC20 asset) internal pure returns (ERC20) {
if (asset == WETH) return ERC20(Denominations.ETH);
if (asset == WBTC) return ERC20(Denominations.BTC);
return 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,
ERC20 quoteAsset,
uint8 quoteAssetDecimals
) internal view returns (uint256 exchangeRate) {
exchangeRate = getValueInUSD(baseAsset).mulDivDown(10**quoteAssetDecimals, getValueInUSD(quoteAsset));
}
/**
* @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 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);
// =========================================== CHAINLINK PRICING FUNCTIONS ===========================================\
/**
* @notice Feed Registry contract used to get chainlink data feeds, use getFeed!!
*/
FeedRegistryInterface public constant feedRegistry =
FeedRegistryInterface(0x47Fb2585D2C56Fe188D0E6ec628a38b74fCeeeDf);
/**
* @notice Could not find an asset's price in USD or ETH.
* @param asset address of the asset
*/
error PriceRouter__PriceNotAvailable(address asset);
/**
* @notice Interacts with Chainlink feed registry and first tries to get `asset` price in USD,
* if that fails, then it tries to get `asset` price in ETH, and then converts the answer into USD.
* @param asset the ERC20 token to get the price of.
* @return price the price of `asset` in USD
*/
function getValueInUSD(ERC20 asset) public view returns (uint256 price) {
AssetConfig memory config = getAssetConfig[asset];
// Make sure asset is supported.
if (!config.isSupported) revert PriceRouter__UnsupportedAsset(address(asset));
// Remap asset if need be.
asset = _remap(asset);
if (!config.isPriceRangeInETH) {
// Price feed is in USD.
(, int256 _price, , uint256 _timestamp, ) = feedRegistry.latestRoundData(address(asset), Denominations.USD);
price = _price.toUint256();
_checkPriceFeed(asset, price, _timestamp, config);
} else {
// Price feed is in ETH.
(, int256 _price, , uint256 _timestamp, ) = feedRegistry.latestRoundData(address(asset), Denominations.ETH);
price = _price.toUint256();
_checkPriceFeed(asset, price, _timestamp, config);
// Convert price from ETH to USD.
price = _price.toUint256().mulWadDown(_getExchangeRateFromETHToUSD());
}
}
/**
* @notice Could not find an asset's price range in USD or ETH.
* @param asset address of the asset
*/
error PriceRouter__PriceRangeNotAvailable(address asset);
/**
* @notice Interacts with Chainlink feed registry and first tries to get `asset` price range in USD,
* if that fails, then it tries to get `asset` price range in ETH, and then converts the range into USD.
* @param asset the ERC20 token to get the price range of.
* @return min the minimum price where Chainlink nodes stop updating the oracle
* @return max the maximum price where Chainlink nodes stop updating the oracle
*/
function _getPriceRange(ERC20 asset)
internal
view
returns (
uint256 min,
uint256 max,
bool isETH
)
{
try feedRegistry.getFeed(address(asset), Denominations.USD) returns (AggregatorV2V3Interface aggregator) {
IChainlinkAggregator chainlinkAggregator = IChainlinkAggregator(address(aggregator));
min = uint256(uint192(chainlinkAggregator.minAnswer()));
max = uint256(uint192(chainlinkAggregator.maxAnswer()));
isETH = false;
} catch {
// If we can't find the USD price, then try the ETH price.
try feedRegistry.getFeed(address(asset), Denominations.ETH) returns (AggregatorV2V3Interface aggregator) {
IChainlinkAggregator chainlinkAggregator = IChainlinkAggregator(address(aggregator));
min = uint256(uint192(chainlinkAggregator.minAnswer()));
max = uint256(uint192(chainlinkAggregator.maxAnswer()));
isETH = true;
} catch {
revert PriceRouter__PriceRangeNotAvailable(address(asset));
}
}
}
/**
* @notice helper function to grab pricing data for ETH in USD
* @return exchangeRate the exchange rate for ETH in terms of USD
* @dev It is inefficient to re-calculate _checkPriceFeed for ETH -> USD multiple times for a single TX,
* but this is done in the explicit way because it is simpler and less prone to logic errors.
*/
function _getExchangeRateFromETHToUSD() internal view returns (uint256 exchangeRate) {
(, int256 _price, , uint256 _timestamp, ) = feedRegistry.latestRoundData(Denominations.ETH, Denominations.USD);
exchangeRate = _price.toUint256();
_checkPriceFeed(WETH, exchangeRate, _timestamp, getAssetConfig[WETH]);
}
/**
* @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 config the assets config storing min price, max price, and heartbeat requirements.
*/
function _checkPriceFeed(
ERC20 asset,
uint256 value,
uint256 timestamp,
AssetConfig memory config
) internal view {
uint256 minPrice = config.minPrice;
if (value < minPrice) revert PriceRouter__AssetBelowMinPrice(address(asset), value, minPrice);
uint256 maxPrice = config.maxPrice;
if (value > maxPrice) revert PriceRouter__AssetAboveMaxPrice(address(asset), value, maxPrice);
uint256 heartbeat = config.heartbeat;
uint256 timeSinceLastUpdate = block.timestamp - timestamp;
if (timeSinceLastUpdate > heartbeat)
revert PriceRouter__StalePrice(address(asset), timeSinceLastUpdate, heartbeat);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
contract Registry is Ownable {
// ============================================= ADDRESS CONFIG =============================================
/**
* @notice Emitted when the address of a contract is changed.
* @param id value representing the unique ID tied to the changed contract
* @param oldAddress address of the contract before the change
* @param newAddress address of the contract after the contract
*/
event AddressChanged(uint256 indexed id, address oldAddress, address newAddress);
/**
* @notice Attempted to set the address of a contract that is not registered.
* @param id id of the contract that is not registered
*/
error Registry__ContractNotRegistered(uint256 id);
/**
* @notice Emitted when depositor privilege changes.
* @param depositor depositor address
* @param state the new state of the depositor privilege
*/
event DepositorOnBehalfChanged(address depositor, bool state);
/**
* @notice The unique ID that the next registered contract will have.
*/
uint256 public nextId;
/**
* @notice Get the address associated with an id.
*/
mapping(uint256 => address) public getAddress;
/**
* @notice In order for an address to make deposits on behalf of users they must be approved.
*/
mapping(address => bool) public approvedForDepositOnBehalf;
/**
* @notice toggles a depositors ability to deposit into cellars on behalf of users.
*/
function setApprovedForDepositOnBehalf(address depositor, bool state) external onlyOwner {
approvedForDepositOnBehalf[depositor] = state;
emit DepositorOnBehalfChanged(depositor, state);
}
/**
* @notice Set the address of the contract at a given id.
*/
function setAddress(uint256 id, address newAddress) external onlyOwner {
if (id >= nextId) revert Registry__ContractNotRegistered(id);
emit AddressChanged(id, getAddress[id], newAddress);
getAddress[id] = newAddress;
}
// ============================================= INITIALIZATION =============================================
/**
* @param gravityBridge address of GravityBridge contract
* @param swapRouter address of SwapRouter contract
* @param priceRouter address of PriceRouter contract
*/
constructor(
address gravityBridge,
address swapRouter,
address priceRouter
) Ownable() {
_register(gravityBridge);
_register(swapRouter);
_register(priceRouter);
}
// ============================================ REGISTER CONFIG ============================================
/**
* @notice Emitted when a new contract is registered.
* @param id value representing the unique ID tied to the new contract
* @param newContract address of the new contract
*/
event Registered(uint256 indexed id, address indexed newContract);
/**
* @notice Register the address of a new contract.
* @param newContract address of the new contract to register
*/
function register(address newContract) external onlyOwner {
_register(newContract);
}
function _register(address newContract) internal {
getAddress[nextId] = newContract;
emit Registered(nextId, newContract);
nextId++;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.4.1) (utils/math/SafeCast.sol)
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) {
require(value >= type(int248).min && value <= type(int248).max, "SafeCast: value doesn't fit in 248 bits");
return int248(value);
}
/**
* @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) {
require(value >= type(int240).min && value <= type(int240).max, "SafeCast: value doesn't fit in 240 bits");
return int240(value);
}
/**
* @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) {
require(value >= type(int232).min && value <= type(int232).max, "SafeCast: value doesn't fit in 232 bits");
return int232(value);
}
/**
* @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) {
require(value >= type(int224).min && value <= type(int224).max, "SafeCast: value doesn't fit in 224 bits");
return int224(value);
}
/**
* @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) {
require(value >= type(int216).min && value <= type(int216).max, "SafeCast: value doesn't fit in 216 bits");
return int216(value);
}
/**
* @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) {
require(value >= type(int208).min && value <= type(int208).max, "SafeCast: value doesn't fit in 208 bits");
return int208(value);
}
/**
* @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) {
require(value >= type(int200).min && value <= type(int200).max, "SafeCast: value doesn't fit in 200 bits");
return int200(value);
}
/**
* @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) {
require(value >= type(int192).min && value <= type(int192).max, "SafeCast: value doesn't fit in 192 bits");
return int192(value);
}
/**
* @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) {
require(value >= type(int184).min && value <= type(int184).max, "SafeCast: value doesn't fit in 184 bits");
return int184(value);
}
/**
* @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) {
require(value >= type(int176).min && value <= type(int176).max, "SafeCast: value doesn't fit in 176 bits");
return int176(value);
}
/**
* @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) {
require(value >= type(int168).min && value <= type(int168).max, "SafeCast: value doesn't fit in 168 bits");
return int168(value);
}
/**
* @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) {
require(value >= type(int160).min && value <= type(int160).max, "SafeCast: value doesn't fit in 160 bits");
return int160(value);
}
/**
* @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) {
require(value >= type(int152).min && value <= type(int152).max, "SafeCast: value doesn't fit in 152 bits");
return int152(value);
}
/**
* @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) {
require(value >= type(int144).min && value <= type(int144).max, "SafeCast: value doesn't fit in 144 bits");
return int144(value);
}
/**
* @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) {
require(value >= type(int136).min && value <= type(int136).max, "SafeCast: value doesn't fit in 136 bits");
return int136(value);
}
/**
* @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) {
require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
return int128(value);
}
/**
* @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) {
require(value >= type(int120).min && value <= type(int120).max, "SafeCast: value doesn't fit in 120 bits");
return int120(value);
}
/**
* @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) {
require(value >= type(int112).min && value <= type(int112).max, "SafeCast: value doesn't fit in 112 bits");
return int112(value);
}
/**
* @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) {
require(value >= type(int104).min && value <= type(int104).max, "SafeCast: value doesn't fit in 104 bits");
return int104(value);
}
/**
* @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) {
require(value >= type(int96).min && value <= type(int96).max, "SafeCast: value doesn't fit in 96 bits");
return int96(value);
}
/**
* @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) {
require(value >= type(int88).min && value <= type(int88).max, "SafeCast: value doesn't fit in 88 bits");
return int88(value);
}
/**
* @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) {
require(value >= type(int80).min && value <= type(int80).max, "SafeCast: value doesn't fit in 80 bits");
return int80(value);
}
/**
* @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) {
require(value >= type(int72).min && value <= type(int72).max, "SafeCast: value doesn't fit in 72 bits");
return int72(value);
}
/**
* @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) {
require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
return int64(value);
}
/**
* @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) {
require(value >= type(int56).min && value <= type(int56).max, "SafeCast: value doesn't fit in 56 bits");
return int56(value);
}
/**
* @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) {
require(value >= type(int48).min && value <= type(int48).max, "SafeCast: value doesn't fit in 48 bits");
return int48(value);
}
/**
* @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) {
require(value >= type(int40).min && value <= type(int40).max, "SafeCast: value doesn't fit in 40 bits");
return int40(value);
}
/**
* @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) {
require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
return int32(value);
}
/**
* @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) {
require(value >= type(int24).min && value <= type(int24).max, "SafeCast: value doesn't fit in 24 bits");
return int24(value);
}
/**
* @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) {
require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
return int16(value);
}
/**
* @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) {
require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
return int8(value);
}
/**
* @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: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Multicall } from "src/base/Multicall.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.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 SafeERC20 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];
}
/**
* @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
})
);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol)
pragma solidity ^0.8.0;
import "./ECDSA.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
* thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
* they need in their contracts using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* _Available since v3.4._
*/
abstract contract EIP712 {
/* solhint-disable var-name-mixedcase */
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;
address private immutable _CACHED_THIS;
bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
/* solhint-enable var-name-mixedcase */
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
bytes32 hashedName = keccak256(bytes(name));
bytes32 hashedVersion = keccak256(bytes(version));
bytes32 typeHash = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_CACHED_CHAIN_ID = block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
_CACHED_THIS = address(this);
_TYPE_HASH = typeHash;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
}
}
function _buildDomainSeparator(
bytes32 typeHash,
bytes32 nameHash,
bytes32 versionHash
) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/extensions/draft-ERC20Permit.sol)
pragma solidity ^0.8.0;
import "./draft-IERC20Permit.sol";
import "../ERC20.sol";
import "../../../utils/cryptography/draft-EIP712.sol";
import "../../../utils/cryptography/ECDSA.sol";
import "../../../utils/Counters.sol";
/**
* @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* _Available since v3.4._
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
using Counters for Counters.Counter;
mapping(address => Counters.Counter) private _nonces;
// solhint-disable-next-line var-name-mixedcase
bytes32 private constant _PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
* However, to ensure consistency with the upgradeable transpiler, we will continue
* to reserve a slot.
* @custom:oz-renamed-from _PERMIT_TYPEHASH
*/
// solhint-disable-next-line var-name-mixedcase
bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC20 token name.
*/
constructor(string memory name) EIP712(name, "1") {}
/**
* @dev See {IERC20Permit-permit}.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual override {
require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");
_approve(owner, spender, value);
}
/**
* @dev See {IERC20Permit-nonces}.
*/
function nonces(address owner) public view virtual override returns (uint256) {
return _nonces[owner].current();
}
/**
* @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view override returns (bytes32) {
return _domainSeparatorV4();
}
/**
* @dev "Consume a nonce": return the current value and increment.
*
* _Available since v4.1._
*/
function _useNonce(address owner) internal virtual returns (uint256 current) {
Counters.Counter storage nonce = _nonces[owner];
current = nonce.current();
nonce.increment();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
{
"compilationTarget": {
"src/base/Cellar.sol": "Cellar"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"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/",
":chainlink/=lib/chainlink/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":solmate/=lib/solmate/src/"
]
}
[{"inputs":[{"internalType":"contract Registry","name":"_registry","type":"address"},{"internalType":"contract ERC20","name":"_asset","type":"address"},{"internalType":"address[]","name":"_positions","type":"address[]"},{"internalType":"enum Cellar.PositionType[]","name":"_positionTypes","type":"uint8[]"},{"internalType":"address","name":"_holdingPosition","type":"address"},{"internalType":"enum Cellar.WithdrawType","name":"_withdrawType","type":"uint8"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"address","name":"_strategistPayout","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":[],"name":"Cellar__ContractNotShutdown","type":"error"},{"inputs":[],"name":"Cellar__ContractShutdown","type":"error"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"uint256","name":"maxDeposit","type":"uint256"}],"name":"Cellar__DepositRestricted","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__InvalidCosmosAddress","type":"error"},{"inputs":[],"name":"Cellar__InvalidFee","type":"error"},{"inputs":[],"name":"Cellar__InvalidFeeCut","type":"error"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"Cellar__InvalidPosition","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__PayoutNotSet","type":"error"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"Cellar__PositionAlreadyUsed","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxPositions","type":"uint256"}],"name":"Cellar__PositionArrayFull","type":"error"},{"inputs":[{"internalType":"address","name":"position","type":"address"},{"internalType":"uint256","name":"sharesRemaining","type":"uint256"}],"name":"Cellar__PositionNotEmpty","type":"error"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"Cellar__PositionPricingNotSetUp","type":"error"},{"inputs":[],"name":"Cellar__RemoveHoldingPosition","type":"error"},{"inputs":[{"internalType":"uint256","name":"blockSharesAreUnlocked","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":[{"internalType":"address","name":"position","type":"address"}],"name":"Cellar__UntrustedPosition","type":"error"},{"inputs":[],"name":"Cellar__WrongSwapParams","type":"error"},{"inputs":[],"name":"Cellar__ZeroAssets","type":"error"},{"inputs":[],"name":"Cellar__ZeroShares","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","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":"uint256","name":"oldLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"DepositLimitChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"oldFeesDistributor","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"newFeesDistributor","type":"bytes32"}],"name":"FeesDistributorChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newHighWatermark","type":"uint256"}],"name":"HighWatermarkReset","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldPosition","type":"address"},{"indexed":true,"internalType":"address","name":"newPosition","type":"address"}],"name":"HoldingPositionChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"LiquidityLimitChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"oldPerformanceFee","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newPerformanceFee","type":"uint64"}],"name":"PerformanceFeeChanged","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":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"PositionAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"PositionRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newPosition1","type":"address"},{"indexed":true,"internalType":"address","name":"newPosition2","type":"address"},{"indexed":false,"internalType":"uint256","name":"index1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index2","type":"uint256"}],"name":"PositionSwapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PulledFromPosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fromPosition","type":"address"},{"indexed":true,"internalType":"address","name":"toPosition","type":"address"},{"indexed":false,"internalType":"uint256","name":"assetsFrom","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"assetsTo","type":"uint256"}],"name":"Rebalance","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":"feesInSharesRedeemed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feesInAssetsSent","type":"uint256"}],"name":"SendFees","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":"oldPerformanceCut","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newPerformanceCut","type":"uint64"}],"name":"StrategistPerformanceCutChanged","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":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"bool","name":"isTrusted","type":"bool"}],"name":"TrustChanged","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"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"enum Cellar.WithdrawType","name":"oldType","type":"uint8"},{"indexed":false,"internalType":"enum Cellar.WithdrawType","name":"newType","type":"uint8"}],"name":"WithdrawTypeChanged","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"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_PERFORMANCE_FEE","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":"uint8","name":"","type":"uint8"}],"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":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"address","name":"position","type":"address"}],"name":"addPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","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":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"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":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","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":[],"name":"depositLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeData","outputs":[{"internalType":"uint256","name":"highWatermark","type":"uint256"},{"internalType":"uint64","name":"strategistPerformanceCut","type":"uint64"},{"internalType":"uint64","name":"strategistPlatformCut","type":"uint64"},{"internalType":"uint64","name":"platformFee","type":"uint64"},{"internalType":"uint64","name":"performanceFee","type":"uint64"},{"internalType":"bytes32","name":"feesDistributor","type":"bytes32"},{"internalType":"address","name":"strategistPayoutAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"getPositionType","outputs":[{"internalType":"enum Cellar.PositionType","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPositions","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"holdingPosition","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initiateShutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"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":[{"internalType":"address","name":"","type":"address"}],"name":"isTrusted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastAccrual","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liftShutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"liquidityLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","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":"uint256","name":"","type":"uint256"}],"name":"positions","outputs":[{"internalType":"address","name":"","type":"address"}],"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":[{"internalType":"address","name":"position","type":"address"}],"name":"pushPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fromPosition","type":"address"},{"internalType":"address","name":"toPosition","type":"address"},{"internalType":"uint256","name":"assetsFrom","type":"uint256"},{"internalType":"enum SwapRouter.Exchange","name":"exchange","type":"uint8"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"rebalance","outputs":[{"internalType":"uint256","name":"assetsTo","type":"uint256"}],"stateMutability":"nonpayable","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":"uint256","name":"index","type":"uint256"}],"name":"removePosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"resetHighWatermark","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sendFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"setDepositLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"newFeesDistributor","type":"bytes32"}],"name":"setFeesDistributor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newHoldingPosition","type":"address"}],"name":"setHoldingPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"setLiquidityLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"newPerformanceFee","type":"uint64"}],"name":"setPerformanceFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"newPlatformFee","type":"uint64"}],"name":"setPlatformFee","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":"setStrategistPerformanceCut","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"cut","type":"uint64"}],"name":"setStrategistPlatformCut","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum Cellar.WithdrawType","name":"newWithdrawType","type":"uint8"}],"name":"setWithdrawType","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shareLockPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index1","type":"uint256"},{"internalType":"uint256","name":"index2","type":"uint256"}],"name":"swapPositions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","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":"position","type":"address"},{"internalType":"enum Cellar.PositionType","name":"positionType","type":"uint8"}],"name":"trustPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userShareLockStartBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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"},{"inputs":[],"name":"withdrawType","outputs":[{"internalType":"enum Cellar.WithdrawType","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]