// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
import { ERC4626, ERC20, SafeTransferLib } from "./base/ERC4626.sol";
import { Multicall } from "./base/Multicall.sol";
import { Ownable } from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import { IAaveV2StablecoinCellar } from "./interfaces/IAaveV2StablecoinCellar.sol";
import { IAaveIncentivesController } from "./interfaces/IAaveIncentivesController.sol";
import { IStakedTokenV2 } from "./interfaces/IStakedTokenV2.sol";
import { ICurveSwaps } from "./interfaces/ICurveSwaps.sol";
import { ISushiSwapRouter } from "./interfaces/ISushiSwapRouter.sol";
import { IGravity } from "./interfaces/IGravity.sol";
import { ILendingPool } from "./interfaces/ILendingPool.sol";
import { Math } from "./utils/Math.sol";
import "./Errors.sol";
/**
* @title Sommelier Aave V2 Stablecoin Cellar
* @notice Dynamic ERC4626 that changes positions to always get the best yield for stablecoins on Aave.
* @author Brian Le
*/
contract AaveV2StablecoinCellar is IAaveV2StablecoinCellar, ERC4626, Multicall, Ownable {
using SafeTransferLib for ERC20;
using Math for uint256;
// ======================================== POSITION STORAGE ========================================
/**
* @notice An interest-bearing derivative of the current asset returned by Aave for lending
* the current asset. Represents cellar's portion of assets earning yield in a lending
* position.
*/
ERC20 public assetAToken;
/**
* @notice The decimals of precision used by the current position's asset.
* @dev Since some stablecoins don't use the standard 18 decimals of precision (eg. USDC and USDT),
* we cache this to use for more efficient decimal conversions.
*/
uint8 public assetDecimals;
/**
* @notice The total amount of assets held in the current position since the time of last accrual.
* @dev Unlike `totalAssets`, this includes locked yield that hasn't been distributed.
*/
uint256 public totalBalance;
// ======================================== ACCRUAL CONFIG ========================================
/**
* @notice Period of time over which yield since the last accrual is linearly distributed to the cellar.
* @dev Net gains are distributed gradually over a period to prevent frontrunning and sandwich attacks.
* Net losses are realized immediately otherwise users could time exits to sidestep losses.
*/
uint32 public accrualPeriod = 7 days;
/**
* @notice Timestamp of when the last accrual occurred.
*/
uint64 public lastAccrual;
/**
* @notice The amount of yield to be distributed to the cellar from the last accrual.
*/
uint160 public maxLocked;
/**
* @notice The minimum level of total balance a strategy provider needs to achieve to receive
* performance fees for the next accrual.
*/
uint256 public highWatermarkBalance;
/**
* @notice Set the accrual period over which yield is distributed.
* @param newAccrualPeriod period of time in seconds of the new accrual period
*/
function setAccrualPeriod(uint32 newAccrualPeriod) external onlyOwner {
// Ensure that the change is not disrupting a currently ongoing distribution of accrued yield.
if (totalLocked() > 0) revert STATE_AccrualOngoing();
emit AccrualPeriodChanged(accrualPeriod, newAccrualPeriod);
accrualPeriod = newAccrualPeriod;
}
// ========================================= FEES CONFIG =========================================
/**
* @notice The percentage of yield accrued as performance fees.
* @dev This should be a value out of 1e18 (ie. 1e18 represents 100%, 0 represents 0%).
*/
uint64 public constant platformFee = 0.0025e18; // 0.25%
/**
* @notice The percentage of total assets accrued as platform fees over a year.
* @dev This should be a value out of 1e18 (ie. 1e18 represents 100%, 0 represents 0%).
*/
uint64 public constant performanceFee = 0.1e18; // 10%
/**
* @notice Cosmos address of module that distributes fees, specified as a hex value.
* @dev The Gravity contract expects a 32-byte value formatted in a specific way.
*/
bytes32 public feesDistributor = hex"000000000000000000000000b813554b423266bbd4c16c32fa383394868c1f55";
/**
* @notice Set the address of the fee distributor on the Sommelier chain.
* @dev IMPORTANT: Ensure that the address is formatted in the specific way that the Gravity contract
* expects it to be.
* @param newFeesDistributor formatted address of the new fee distributor module
*/
function setFeesDistributor(bytes32 newFeesDistributor) external onlyOwner {
emit FeesDistributorChanged(feesDistributor, newFeesDistributor);
feesDistributor = newFeesDistributor;
}
// ======================================== TRUST CONFIG ========================================
/**
* @notice Whether an asset position is trusted or not. Prevents cellar from rebalancing into an
* asset that has not been trusted by the users. Trusting / distrusting of an asset is done
* through governance.
*/
mapping(ERC20 => bool) public isTrusted;
/**
* @notice Set the trust for a position.
* @param position address of an asset position on Aave (eg. FRAX, UST, FEI).
* @param trust whether to trust or distrust
*/
function setTrust(ERC20 position, bool trust) external onlyOwner {
isTrusted[position] = trust;
// In the case that validators no longer trust the current position, pull all assets back
// into the cellar.
ERC20 currentPosition = asset;
if (trust == false && position == currentPosition) _emptyPosition(currentPosition);
emit TrustChanged(address(position), trust);
}
// ======================================== LIMITS CONFIG ========================================
/**
* @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;
/**
* @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;
/**
* @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 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 STATE_ContractShutdown();
_;
}
/**
* @notice Shutdown the cellar. Used in an emergency or if the cellar has been deprecated.
* @param emptyPosition whether to pull all assets back into the cellar from the current position
*/
function initiateShutdown(bool emptyPosition) external whenNotShutdown onlyOwner {
// Pull all assets from a position.
if (emptyPosition) _emptyPosition(asset);
isShutdown = true;
emit ShutdownInitiated(emptyPosition);
}
/**
* @notice Restart the cellar.
*/
function liftShutdown() external onlyOwner {
isShutdown = false;
emit ShutdownLifted();
}
// ======================================== INITIALIZATION ========================================
/**
* @notice Curve Registry Exchange contract. Used for rebalancing positions.
*/
ICurveSwaps public immutable curveRegistryExchange; // 0x81C46fECa27B31F3ADC2b91eE4be9717d1cd3DD7
/**
* @notice SushiSwap Router V2 contract. Used for reinvesting rewards back into the current position.
*/
ISushiSwapRouter public immutable sushiswapRouter; // 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F
/**
* @notice Aave Lending Pool V2 contract. Used to deposit and withdraw from the current position.
*/
ILendingPool public immutable lendingPool; // 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9
/**
* @notice Aave Incentives Controller V2 contract. Used to claim and unstake rewards to reinvest.
*/
IAaveIncentivesController public immutable incentivesController; // 0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5
/**
* @notice Cosmos Gravity Bridge contract. Used to transfer fees to `feeDistributor` on the Sommelier chain.
*/
IGravity public immutable gravityBridge; // 0x69592e6f9d21989a043646fE8225da2600e5A0f7
/**
* @notice stkAAVE address. Used to swap rewards to the current asset to reinvest.
*/
IStakedTokenV2 public immutable stkAAVE; // 0x4da27a545c0c5B758a6BA100e3a049001de870f5
/**
* @notice AAVE address. Used to swap rewards to the current asset to reinvest.
*/
ERC20 public immutable AAVE; // 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9
/**
* @notice WETH address. Used to swap rewards to the current asset to reinvest.
*/
ERC20 public immutable WETH; // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
/**
* @dev Owner will 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 _asset current asset managed by the cellar
* @param _approvedPositions list of approved positions to start with
* @param _curveRegistryExchange Curve registry exchange
* @param _sushiswapRouter Sushiswap V2 router address
* @param _lendingPool Aave V2 lending pool address
* @param _incentivesController _incentivesController
* @param _gravityBridge Cosmos Gravity Bridge address
* @param _stkAAVE stkAAVE address
* @param _AAVE AAVE address
* @param _WETH WETH address
*/
constructor(
ERC20 _asset,
ERC20[] memory _approvedPositions,
ICurveSwaps _curveRegistryExchange,
ISushiSwapRouter _sushiswapRouter,
ILendingPool _lendingPool,
IAaveIncentivesController _incentivesController,
IGravity _gravityBridge,
IStakedTokenV2 _stkAAVE,
ERC20 _AAVE,
ERC20 _WETH
) ERC4626(_asset, "Sommelier Aave V2 Stablecoin Cellar LP Token", "aave2-CLR-S", 18) {
// Initialize immutables.
curveRegistryExchange = _curveRegistryExchange;
sushiswapRouter = _sushiswapRouter;
lendingPool = _lendingPool;
incentivesController = _incentivesController;
gravityBridge = _gravityBridge;
stkAAVE = _stkAAVE;
AAVE = _AAVE;
WETH = _WETH;
// Initialize asset.
isTrusted[_asset] = true;
uint8 _assetDecimals = _updatePosition(_asset);
// Initialize limits.
uint256 powOfAssetDecimals = 10**_assetDecimals;
liquidityLimit = 5_000_000 * powOfAssetDecimals;
depositLimit = type(uint256).max;
// Initialize approved positions.
for (uint256 i; i < _approvedPositions.length; i++) isTrusted[_approvedPositions[i]] = true;
// Initialize starting timestamp for first accrual.
lastAccrual = uint32(block.timestamp);
// Transfer ownership to the Gravity Bridge.
transferOwnership(address(_gravityBridge));
}
// ============================================ CORE LOGIC ============================================
function deposit(uint256 assets, address receiver) public override returns (uint256 shares) {
// Check that the deposit is not restricted by a deposit limit or liquidity limit and
// prevent deposits during a shutdown.
uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) revert USR_DepositRestricted(assets, maxAssets);
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
ERC20 cellarAsset = asset;
uint256 assetsBeforeDeposit = cellarAsset.balanceOf(address(this));
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
// Check that the balance transferred is what was expected.
uint256 assetsReceived = cellarAsset.balanceOf(address(this)) - assetsBeforeDeposit;
if (assetsReceived != assets) revert STATE_AssetUsesFeeOnTransfer(address(cellarAsset));
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
function mint(uint256 shares, address receiver) public override returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Check that the deposit is not restricted by a deposit limit or liquidity limit and
// prevent deposits during a shutdown.
uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) revert USR_DepositRestricted(assets, maxAssets);
ERC20 cellarAsset = asset;
uint256 assetsBeforeDeposit = cellarAsset.balanceOf(address(this));
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
// Check that the balance transferred is what was expected.
uint256 assetsReceived = cellarAsset.balanceOf(address(this)) - assetsBeforeDeposit;
if (assetsReceived != assets) revert STATE_AssetUsesFeeOnTransfer(address(cellarAsset));
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/**
* @dev Check if holding position has enough funds to cover the withdraw and only pull from the
* current lending position if needed.
* @param assets amount of assets to withdraw
*/
function beforeWithdraw(
uint256 assets,
uint256,
address,
address
) internal override {
ERC20 currentPosition = asset;
uint256 holdings = totalHoldings();
// Only withdraw if not enough assets in the holding pool.
if (assets > holdings) {
uint256 withdrawnAssets = _withdrawFromPosition(currentPosition, assets - holdings);
totalBalance -= withdrawnAssets;
highWatermarkBalance -= withdrawnAssets;
}
}
// ======================================= ACCOUNTING LOGIC =======================================
/**
* @notice The total amount of assets in the cellar.
* @dev Excludes locked yield that hasn't been distributed.
*/
function totalAssets() public view override returns (uint256) {
return totalBalance + totalHoldings() - totalLocked();
}
/**
* @notice The total amount of assets in holding position.
*/
function totalHoldings() public view returns (uint256) {
return asset.balanceOf(address(this));
}
/**
* @notice The total amount of locked yield still being distributed.
*/
function totalLocked() public view returns (uint256) {
// Get the last accrual and accrual period.
uint256 previousAccrual = lastAccrual;
uint256 accrualInterval = accrualPeriod;
// If the accrual period has passed, there is no locked yield.
if (block.timestamp >= previousAccrual + accrualInterval) return 0;
// Get the maximum amount we could return.
uint256 maxLockedYield = maxLocked;
// Get how much yield remains locked.
return maxLockedYield - (maxLockedYield * (block.timestamp - previousAccrual)) / accrualInterval;
}
/**
* @notice The amount of assets that the cellar would exchange for the amount of shares provided.
* @param shares amount of shares to convert
* @return assets the shares can be exchanged for
*/
function convertToAssets(uint256 shares) public view override returns (uint256 assets) {
uint256 totalShares = totalSupply;
assets = totalShares == 0
? shares.changeDecimals(18, assetDecimals)
: shares.mulDivDown(totalAssets(), totalShares);
}
/**
* @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) {
uint256 totalShares = totalSupply;
shares = totalShares == 0
? assets.changeDecimals(assetDecimals, 18)
: assets.mulDivDown(totalShares, 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 totalShares = totalSupply;
assets = totalShares == 0
? shares.changeDecimals(18, assetDecimals)
: shares.mulDivUp(totalAssets(), totalShares);
}
/**
* @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 totalShares = totalSupply;
shares = totalShares == 0
? assets.changeDecimals(assetDecimals, 18)
: assets.mulDivUp(totalShares, totalAssets());
}
// ========================================= LIMITS LOGIC =========================================
/**
* @notice Total amount of assets that can be deposited for a user.
* @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;
(uint256 leftUntilDepositLimit, uint256 leftUntilLiquidityLimit) = _getAssetsLeftUntilLimits(
asssetDepositLimit,
asssetLiquidityLimit,
receiver
);
// 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.
* @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 shares) {
if (isShutdown) return 0;
uint256 asssetDepositLimit = depositLimit;
uint256 asssetLiquidityLimit = liquidityLimit;
if (asssetDepositLimit == type(uint256).max && asssetLiquidityLimit == type(uint256).max)
return type(uint256).max;
(uint256 leftUntilDepositLimit, uint256 leftUntilLiquidityLimit) = _getAssetsLeftUntilLimits(
asssetDepositLimit,
asssetLiquidityLimit,
receiver
);
// Only return the more relevant of the two.
shares = convertToShares(Math.min(leftUntilDepositLimit, leftUntilLiquidityLimit));
}
function _getAssetsLeftUntilLimits(
uint256 asssetDepositLimit,
uint256 asssetLiquidityLimit,
address receiver
) internal view returns (uint256 leftUntilDepositLimit, uint256 leftUntilLiquidityLimit) {
uint256 totalAssetsIncludingUnrealizedGains = assetAToken.balanceOf(address(this)) + totalHoldings();
// Convert receiver's shares to assets using total assets including locked yield.
uint256 receiverShares = balanceOf[receiver];
uint256 totalShares = totalSupply;
uint256 maxWithdrawableByReceiver = totalShares == 0
? receiverShares
: receiverShares.mulDivDown(totalAssetsIncludingUnrealizedGains, totalShares);
// Get the maximum amount of assets that can be deposited until limits are reached.
leftUntilDepositLimit = asssetDepositLimit.subMinZero(maxWithdrawableByReceiver);
leftUntilLiquidityLimit = asssetLiquidityLimit.subMinZero(totalAssetsIncludingUnrealizedGains);
}
// ========================================== ACCRUAL LOGIC ==========================================
/**
* @notice Accrue yield, platform fees, and performance fees.
* @dev Since this is the function responsible for distributing yield to shareholders and
* updating the cellar's balance, it is important to make sure it gets called regularly.
*/
function accrue() public {
uint256 totalLockedYield = totalLocked();
// Without this check, malicious actors could do a slowdown attack on the distribution of
// yield by continuously resetting the accrual period.
if (msg.sender != owner() && totalLockedYield > 0) revert STATE_AccrualOngoing();
// Compute and store current exchange rate between assets and shares for gas efficiency.
uint256 oneAsset = 10**assetDecimals;
uint256 exchangeRate = convertToShares(oneAsset);
// Get balance since last accrual and updated balance for this accrual.
uint256 balanceThisAccrual = assetAToken.balanceOf(address(this));
// Calculate platform fees accrued.
uint256 elapsedTime = block.timestamp - lastAccrual;
uint256 platformFeeInAssets = (balanceThisAccrual * elapsedTime * platformFee) / 1e18 / 365 days;
uint256 platformFees = platformFeeInAssets.mulDivDown(exchangeRate, oneAsset); // Convert to shares.
// Calculate performance fees accrued.
uint256 yield = balanceThisAccrual.subMinZero(highWatermarkBalance);
uint256 performanceFeeInAssets = yield.mulWadDown(performanceFee);
uint256 performanceFees = performanceFeeInAssets.mulDivDown(exchangeRate, oneAsset); // Convert to shares.
// Mint accrued fees as shares.
_mint(address(this), platformFees + performanceFees);
// Do not count assets set aside for fees as yield. Allows fees to be immediately withdrawable.
maxLocked = uint160(totalLockedYield + yield.subMinZero(platformFeeInAssets + performanceFeeInAssets));
lastAccrual = uint32(block.timestamp);
totalBalance = balanceThisAccrual;
// Only update high watermark if balance greater than last high watermark.
if (balanceThisAccrual > highWatermarkBalance) highWatermarkBalance = balanceThisAccrual;
emit Accrual(platformFees, performanceFees, yield);
}
// ========================================= POSITION LOGIC =========================================
/**
* @notice Pushes assets into the current Aave lending position.
* @param assets amount of assets to enter into the current position
*/
function enterPosition(uint256 assets) public whenNotShutdown onlyOwner {
ERC20 currentPosition = asset;
totalBalance += assets;
// Without this line, assets entered into Aave would be counted as gains during the next
// accrual.
highWatermarkBalance += assets;
_depositIntoPosition(currentPosition, assets);
emit EnterPosition(address(currentPosition), assets);
}
/**
* @notice Pushes all assets in holding into the current Aave lending position.
*/
function enterPosition() external {
enterPosition(totalHoldings());
}
/**
* @notice Pulls assets from the current Aave lending position.
* @param assets amount of assets to exit from the current position
*/
function exitPosition(uint256 assets) public whenNotShutdown onlyOwner {
ERC20 currentPosition = asset;
uint256 withdrawnAssets = _withdrawFromPosition(currentPosition, assets);
totalBalance -= withdrawnAssets;
// Without this line, assets exited from Aave would be counted as losses during the next
// accrual.
highWatermarkBalance -= withdrawnAssets;
emit ExitPosition(address(currentPosition), assets);
}
/**
* @notice Pulls all assets from the current Aave lending position.
* @dev Strategy providers should not assume the position is empty after this call. If there is
* unrealized yield, that will still remain in the position. To completely empty the cellar,
* multicall accrue and this.
*/
function exitPosition() external {
exitPosition(totalBalance);
}
/**
* @notice Rebalances current assets into a new position.
* @param route array of [initial token, pool, token, pool, token, ...] that specifies the swap route on Curve.
* @param swapParams multidimensional array of [i, j, swap type] where i and j are the correct
values for the n'th pool in `_route` and swap type should be 1 for a
stableswap `exchange`, 2 for stableswap `exchange_underlying`, 3 for a
cryptoswap `exchange`, 4 for a cryptoswap `exchange_underlying` and 5 for
Polygon factory metapools `exchange_underlying`
* @param minAssetsOut minimum amount of assets received after swap
*/
function rebalance(
address[9] memory route,
uint256[3][4] memory swapParams,
uint256 minAssetsOut
) external whenNotShutdown onlyOwner {
// Retrieve the last token in the route and store it as the new asset position.
ERC20 newPosition;
for (uint256 i; ; i += 2) {
if (i == 8 || route[i + 1] == address(0)) {
newPosition = ERC20(route[i]);
break;
}
}
// Ensure the asset position is trusted.
if (!isTrusted[newPosition]) revert USR_UntrustedPosition(address(newPosition));
ERC20 oldPosition = asset;
// Doesn't make sense to rebalance into the same position.
if (newPosition == oldPosition) revert USR_SamePosition(address(oldPosition));
// Store this for later when updating total balance.
uint256 totalAssetsInHolding = totalHoldings();
uint256 totalBalanceIncludingHoldings = totalBalance + totalAssetsInHolding;
// Pull any assets in the lending position back in to swap everything into the new position.
uint256 assetsBeforeSwap = assetAToken.balanceOf(address(this)) > 0
? _withdrawFromPosition(oldPosition, type(uint256).max) + totalAssetsInHolding
: totalAssetsInHolding;
// Perform stablecoin swap using Curve.
oldPosition.safeApprove(address(curveRegistryExchange), assetsBeforeSwap);
uint256 assetsAfterSwap = curveRegistryExchange.exchange_multiple(
route,
swapParams,
assetsBeforeSwap,
minAssetsOut
);
uint8 oldPositionDecimals = assetDecimals;
// Updates state for new position and check that Aave supports it.
uint8 newPositionDecimals = _updatePosition(newPosition);
// Deposit all newly swapped assets into Aave.
_depositIntoPosition(newPosition, assetsAfterSwap);
// Update maximum locked yield to scale accordingly to the decimals of the new asset.
maxLocked = uint160(uint256(maxLocked).changeDecimals(oldPositionDecimals, newPositionDecimals));
// Update the cellar's balance. If the unrealized gains before rebalancing exceed the losses
// from the swap, then losses will be taken from the unrealized gains during next accrual
// and this rebalance will not effect the exchange rate of shares to assets. Otherwise, the
// losses from this rebalance will be realized and factored into the new balance.
uint256 newTotalBalance = Math.min(
totalBalanceIncludingHoldings.changeDecimals(oldPositionDecimals, newPositionDecimals),
assetsAfterSwap
);
totalBalance = newTotalBalance;
// Keep high watermark at level it should be at before rebalance because otherwise swap
// losses from this rebalance would not be counted in the next accrual. Include holdings
// into new high watermark balance as those have all been deposited into Aave now.
highWatermarkBalance = (highWatermarkBalance + totalAssetsInHolding).changeDecimals(
oldPositionDecimals,
newPositionDecimals
);
emit Rebalance(address(oldPosition), address(newPosition), newTotalBalance);
}
// ======================================= REINVEST LOGIC =======================================
/**
* @notice Claim rewards from Aave and begin cooldown period to unstake them.
* @return rewards amount of stkAAVE rewards claimed from Aave
*/
function claimAndUnstake() external onlyOwner returns (uint256 rewards) {
// Necessary to do as `claimRewards` accepts a dynamic array as first param.
address[] memory aToken = new address[](1);
aToken[0] = address(assetAToken);
// Claim all stkAAVE rewards.
rewards = incentivesController.claimRewards(aToken, type(uint256).max, address(this));
// Begin the 10 day cooldown period for unstaking stkAAVE for AAVE.
stkAAVE.cooldown();
emit ClaimAndUnstake(rewards);
}
/**
* @notice Reinvest rewards back into cellar's current position.
* @dev Must be called within 2 day unstake period 10 days after `claimAndUnstake` was run.
* @param minAssetsOut minimum amount of assets to receive after swapping AAVE to the current asset
*/
function reinvest(uint256 minAssetsOut) external onlyOwner {
// Redeems the cellar's stkAAVE rewards for AAVE.
stkAAVE.redeem(address(this), type(uint256).max);
// Get the amount of AAVE rewards going in to be swap for the current asset.
uint256 rewardsIn = AAVE.balanceOf(address(this));
ERC20 currentAsset = asset;
// Specify the swap path from AAVE -> WETH -> current asset.
address[] memory path = new address[](3);
path[0] = address(AAVE);
path[1] = address(WETH);
path[2] = address(currentAsset);
// Perform a multihop swap using Sushiswap.
AAVE.safeApprove(address(sushiswapRouter), rewardsIn);
uint256[] memory amounts = sushiswapRouter.swapExactTokensForTokens(
rewardsIn,
minAssetsOut,
path,
address(this),
block.timestamp + 60
);
uint256 assetsOut = amounts[amounts.length - 1];
// In the case of a shutdown, we just may want to redeem any leftover rewards for users to
// claim but without entering them back into a position in case the position has been
// exited. Also, for the purposes of performance fee calculation, we count reinvested
// rewards as yield so do not update balance.
if (!isShutdown) _depositIntoPosition(currentAsset, assetsOut);
emit Reinvest(address(currentAsset), rewardsIn, assetsOut);
}
// ========================================= FEES LOGIC =========================================
/**
* @notice Transfer accrued fees to the Sommelier chain to distribute.
* @dev Fees are accrued as shares and redeemed upon transfer.
*/
function sendFees() external onlyOwner {
// Redeem our fee shares for assets to send to the fee distributor module.
uint256 totalFees = balanceOf[address(this)];
uint256 assets = previewRedeem(totalFees);
require(assets != 0, "ZERO_ASSETS");
// Only withdraw assets from position if the holding position does not contain enough funds.
// Pass in only the amount of assets withdrawn, the rest doesn't matter.
beforeWithdraw(assets, 0, address(0), address(0));
_burn(address(this), totalFees);
// Transfer assets to a fee distributor on the Sommelier chain.
ERC20 positionAsset = asset;
positionAsset.safeApprove(address(gravityBridge), assets);
gravityBridge.sendToCosmos(address(positionAsset), feesDistributor, assets);
emit SendFees(totalFees, assets);
}
// ====================================== RECOVERY LOGIC ======================================
/**
* @notice Sweep tokens that are not suppose to be in the cellar.
* @dev This may be used in case the wrong tokens are accidentally sent.
* @param token address of token to transfer out of this cellar
* @param to address to transfer sweeped tokens to
*/
function sweep(ERC20 token, address to) external onlyOwner {
// Prevent sweeping of assets managed by the cellar and shares minted to the cellar as fees.
if (token == asset || token == assetAToken || token == this || address(token) == address(stkAAVE))
revert USR_ProtectedAsset(address(token));
// Transfer out tokens in this cellar that shouldn't be here.
uint256 amount = token.balanceOf(address(this));
token.safeTransfer(to, amount);
emit Sweep(address(token), to, amount);
}
// ===================================== HELPER FUNCTIONS =====================================
/**
* @notice Deposits cellar holdings into an Aave lending position.
* @param position the address of the asset position
* @param assets the amount of assets to deposit
*/
function _depositIntoPosition(ERC20 position, uint256 assets) internal {
// Deposit assets into Aave position.
position.safeApprove(address(lendingPool), assets);
lendingPool.deposit(address(position), assets, address(this), 0);
emit DepositIntoPosition(address(position), assets);
}
/**
* @notice Withdraws assets from an Aave lending position.
* @dev The assets withdrawn differs from the assets specified if withdrawing `type(uint256).max`.
* @param position the address of the asset position
* @param assets amount of assets to withdraw
* @return withdrawnAssets amount of assets actually withdrawn
*/
function _withdrawFromPosition(ERC20 position, uint256 assets) internal returns (uint256 withdrawnAssets) {
// Withdraw assets from Aave position.
withdrawnAssets = lendingPool.withdraw(address(position), assets, address(this));
emit WithdrawFromPosition(address(position), withdrawnAssets);
}
/**
* @notice Pull all assets from the current lending position on Aave back into holding.
* @param position the address of the asset position to pull from
*/
function _emptyPosition(ERC20 position) internal {
uint256 totalPositionBalance = totalBalance;
if (totalPositionBalance > 0) {
accrue();
_withdrawFromPosition(position, type(uint256).max);
delete totalBalance;
delete highWatermarkBalance;
}
}
/**
* @notice Update state variables related to the current position.
* @dev Be aware that when updating to an asset that uses less decimals than the previous
* asset (eg. DAI -> USDC), `depositLimit` and `liquidityLimit` will lose some precision
* due to truncation.
* @param newPosition address of the new asset being managed by the cellar
*/
function _updatePosition(ERC20 newPosition) internal returns (uint8 newAssetDecimals) {
// Retrieve the aToken that will represent the cellar's new position on Aave.
(, , , , , , , address aTokenAddress, , , , ) = lendingPool.getReserveData(address(newPosition));
// If the address is not null, it is supported by Aave.
if (aTokenAddress == address(0)) revert USR_UnsupportedPosition(address(newPosition));
// Update the decimals used by limits if necessary.
uint8 oldAssetDecimals = assetDecimals;
newAssetDecimals = newPosition.decimals();
// Ensure the decimals of precision of the new position uses will not break the cellar.
if (newAssetDecimals > 18) revert USR_TooManyDecimals(newAssetDecimals, 18);
// Ignore if decimals are the same or if it is the first time initializing a position.
if (oldAssetDecimals != 0 && oldAssetDecimals != newAssetDecimals) {
uint256 asssetDepositLimit = depositLimit;
uint256 asssetLiquidityLimit = liquidityLimit;
if (asssetDepositLimit != type(uint256).max)
depositLimit = asssetDepositLimit.changeDecimals(oldAssetDecimals, newAssetDecimals);
if (asssetLiquidityLimit != type(uint256).max)
liquidityLimit = asssetLiquidityLimit.changeDecimals(oldAssetDecimals, newAssetDecimals);
}
// Update state related to the current position.
asset = newPosition;
assetDecimals = newAssetDecimals;
assetAToken = ERC20(aTokenAddress);
}
}
// 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: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import { ERC20 } from "lib/solmate/src/tokens/ERC20.sol";
import { SafeTransferLib } from "lib/solmate/src/utils/SafeTransferLib.sol";
import { Math } from "../utils/Math.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using Math for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol,
uint8 _decimals
) ERC20(_name, _symbol, _decimals) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
beforeDeposit(assets, shares, receiver);
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares, receiver);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
beforeDeposit(assets, shares, receiver);
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares, receiver);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares, receiver, owner);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
afterWithdraw(assets, shares, receiver, owner);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares, receiver, owner);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
afterWithdraw(assets, shares, receiver, owner);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeDeposit(
uint256 assets,
uint256 shares,
address receiver
) internal virtual {}
function afterDeposit(
uint256 assets,
uint256 shares,
address receiver
) internal virtual {}
function beforeWithdraw(
uint256 assets,
uint256 shares,
address receiver,
address owner
) internal virtual {}
function afterWithdraw(
uint256 assets,
uint256 shares,
address receiver,
address owner
) internal virtual {}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
// ========================================== USER ERRORS ===========================================
/**
* @dev These errors represent invalid user input to functions. Where appropriate, the invalid value
* is specified along with constraints. These errors can be resolved by callers updating their
* arguments.
*/
/**
* @notice Attempted an action with zero assets.
*/
error USR_ZeroAssets();
/**
* @notice Attempted an action with zero shares.
*/
error USR_ZeroShares();
/**
* @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 USR_DepositRestricted(uint256 assets, uint256 maxDeposit);
/**
* @notice Attempted to transfer more active shares than the user has.
* @param activeShares amount of shares user has
* @param attemptedActiveShares amount of shares user tried to transfer
*/
error USR_NotEnoughActiveShares(uint256 activeShares, uint256 attemptedActiveShares);
/**
* @notice Attempted swap into an asset that is not the current asset of the position.
* @param assetOut address of the asset attempted to swap to
* @param currentAsset address of the current asset of position
*/
error USR_InvalidSwap(address assetOut, address currentAsset);
/**
* @notice Attempted to sweep an asset that is managed by the cellar.
* @param token address of the token that can't be sweeped
*/
error USR_ProtectedAsset(address token);
/**
* @notice Attempted rebalance into the same position.
* @param position address of the position
*/
error USR_SamePosition(address position);
/**
* @notice Attempted to update the position to one that is not supported by the platform.
* @param unsupportedPosition address of the unsupported position
*/
error USR_UnsupportedPosition(address unsupportedPosition);
/**
* @notice Attempted an operation on an untrusted position.
* @param position address of the position
*/
error USR_UntrustedPosition(address position);
/**
* @notice Attempted to update a position to an asset that uses an incompatible amount of decimals.
* @param newDecimals decimals of precision that the new position uses
* @param maxDecimals maximum decimals of precision for a position to be compatible with the cellar
*/
error USR_TooManyDecimals(uint8 newDecimals, uint8 maxDecimals);
/**
* @notice User attempted to stake zero amout.
*/
error USR_ZeroDeposit();
/**
* @notice User attempted to stake an amount smaller than the minimum deposit.
*
* @param amount Amount user attmpted to stake.
* @param minimumDeposit The minimum deopsit amount accepted.
*/
error USR_MinimumDeposit(uint256 amount, uint256 minimumDeposit);
/**
* @notice The specified deposit ID does not exist for the caller.
*
* @param depositId The deposit ID provided for lookup.
*/
error USR_NoDeposit(uint256 depositId);
/**
* @notice The user is attempting to cancel unbonding for a deposit which is not unbonding.
*
* @param depositId The deposit ID the user attempted to cancel.
*/
error USR_NotUnbonding(uint256 depositId);
/**
* @notice The user is attempting to unbond a deposit which has already been unbonded.
*
* @param depositId The deposit ID the user attempted to unbond.
*/
error USR_AlreadyUnbonding(uint256 depositId);
/**
* @notice The user is attempting to unstake a deposit which is still timelocked.
*
* @param depositId The deposit ID the user attempted to unstake.
*/
error USR_StakeLocked(uint256 depositId);
/**
* @notice The contract owner attempted to update rewards but the new reward rate would cause overflow.
*/
error USR_RewardTooLarge();
/**
* @notice The reward distributor attempted to update rewards but 0 rewards per epoch.
* This can also happen if there is less than 1 wei of rewards per second of the
* epoch - due to integer division this will also lead to 0 rewards.
*/
error USR_ZeroRewardsPerEpoch();
/**
* @notice The caller attempted to stake with a lock value that did not
* correspond to a valid staking time.
*
* @param lock The provided lock value.
*/
error USR_InvalidLockValue(uint256 lock);
/**
* @notice The caller attempted an signed action with an invalid signature.
* @param signatureLength length of the signature passed in
* @param expectedSignatureLength expected length of the signature passed in
*/
error USR_InvalidSignature(uint256 signatureLength, uint256 expectedSignatureLength);
// ========================================== STATE ERRORS ===========================================
/**
* @dev These errors represent actions that are being prevented due to current contract state.
* These errors do not relate to user input, and may or may not be resolved by other actions
* or the progression of time.
*/
/**
* @notice Attempted an action when cellar is using an asset that has a fee on transfer.
* @param assetWithFeeOnTransfer address of the asset with fee on transfer
*/
error STATE_AssetUsesFeeOnTransfer(address assetWithFeeOnTransfer);
/**
* @notice Attempted action was prevented due to contract being shutdown.
*/
error STATE_ContractShutdown();
/**
* @notice Attempted to shutdown the contract when it was already shutdown.
*/
error STATE_AlreadyShutdown();
/**
* @notice The caller attempted to start a reward period, but the contract did not have enough tokens
* for the specified amount of rewards.
*
* @param rewardBalance The amount of distributionToken held by the contract.
* @param reward The amount of rewards the caller attempted to distribute.
*/
error STATE_RewardsNotFunded(uint256 rewardBalance, uint256 reward);
/**
* @notice Attempted an operation that is prohibited while yield is still being distributed from the last accrual.
*/
error STATE_AccrualOngoing();
/**
* @notice The caller attempted to change the epoch length, but current reward epochs were active.
*/
error STATE_RewardsOngoing();
/**
* @notice The caller attempted to change the next epoch duration, but there are rewards ready.
*/
error STATE_RewardsReady();
/**
* @notice The caller attempted to deposit stake, but there are no remaining rewards to pay out.
*/
error STATE_NoRewardsLeft();
/**
* @notice The caller attempted to perform an an emergency unstake, but the contract
* is not in emergency mode.
*/
error STATE_NoEmergencyUnstake();
/**
* @notice The caller attempted to perform an an emergency unstake, but the contract
* is not in emergency mode, or the emergency mode does not allow claiming rewards.
*/
error STATE_NoEmergencyClaim();
/**
* @notice The caller attempted to perform a state-mutating action (e.g. staking or unstaking)
* while the contract was paused.
*/
error STATE_ContractPaused();
/**
* @notice The caller attempted to perform a state-mutating action (e.g. staking or unstaking)
* while the contract was killed (placed in emergency mode).
* @dev Emergency mode is irreversible.
*/
error STATE_ContractKilled();
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
interface IAaveIncentivesController {
event RewardsAccrued(address indexed user, uint256 amount);
event RewardsClaimed(address indexed user, address indexed to, address indexed claimer, uint256 amount);
event ClaimerSet(address indexed user, address indexed claimer);
/*
* @dev Returns the configuration of the distribution for a certain asset
* @param asset The address of the reference asset of the distribution
* @return The asset index, the emission per second and the last updated timestamp
**/
function getAssetData(address asset)
external
view
returns (
uint256,
uint256,
uint256
);
/*
* LEGACY **************************
* @dev Returns the configuration of the distribution for a certain asset
* @param asset The address of the reference asset of the distribution
* @return The asset index, the emission per second and the last updated timestamp
**/
function assets(address asset)
external
view
returns (
uint128,
uint128,
uint256
);
/**
* @dev Whitelists an address to claim the rewards on behalf of another address
* @param user The address of the user
* @param claimer The address of the claimer
*/
function setClaimer(address user, address claimer) external;
/**
* @dev Returns the whitelisted claimer for a certain address (0x0 if not set)
* @param user The address of the user
* @return The claimer address
*/
function getClaimer(address user) external view returns (address);
/**
* @dev Configure assets for a certain rewards emission
* @param assets The assets to incentivize
* @param emissionsPerSecond The emission for each asset
*/
function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond) external;
/**
* @dev Called by the corresponding asset on any update that affects the rewards distribution
* @param asset The address of the user
* @param userBalance The balance of the user of the asset in the lending pool
* @param totalSupply The total supply of the asset in the lending pool
**/
function handleAction(
address asset,
uint256 userBalance,
uint256 totalSupply
) external;
/**
* @dev Returns the total of rewards of an user, already accrued + not yet accrued
* @param user The address of the user
* @return The rewards
**/
function getRewardsBalance(address[] calldata assets, address user) external view returns (uint256);
/**
* @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards
* @param amount Amount of rewards to claim
* @param to Address that will be receiving the rewards
* @return Rewards claimed
**/
function claimRewards(
address[] calldata assets,
uint256 amount,
address to
) external returns (uint256);
/**
* @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating
* the pending rewards. The caller must
* be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager
* @param amount Amount of rewards to claim
* @param user Address to check and claim rewards
* @param to Address that will be receiving the rewards
* @return Rewards claimed
**/
function claimRewardsOnBehalf(
address[] calldata assets,
uint256 amount,
address user,
address to
) external returns (uint256);
/**
* @dev returns the unclaimed rewards of the user
* @param user the address of the user
* @return the unclaimed user rewards
*/
function getUserUnclaimedRewards(address user) external view returns (uint256);
/**
* @dev returns the unclaimed rewards of the user
* @param user the address of the user
* @param asset The asset to incentivize
* @return the user index for the asset
*/
function getUserAssetData(address user, address asset) external view returns (uint256);
/**
* @dev for backward compatibility with previous implementation of the Incentives controller
*/
function REWARD_TOKEN() external view returns (address);
/**
* @dev for backward compatibility with previous implementation of the Incentives controller
*/
function PRECISION() external view returns (uint8);
/**
* @dev Gets the distribution end timestamp of the emissions
*/
function DISTRIBUTION_END() external view returns (uint256);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
import { ERC20 } from "lib/solmate/src/tokens/ERC20.sol";
import { IAaveIncentivesController } from "../interfaces/IAaveIncentivesController.sol";
import { IStakedTokenV2 } from "../interfaces/IStakedTokenV2.sol";
import { ICurveSwaps } from "../interfaces/ICurveSwaps.sol";
import { ISushiSwapRouter } from "../interfaces/ISushiSwapRouter.sol";
import { ILendingPool } from "../interfaces/ILendingPool.sol";
import { IGravity } from "../interfaces/IGravity.sol";
/**
* @title Interface for AaveV2StablecoinCellar
*/
interface IAaveV2StablecoinCellar {
// ======================================== POSITION STORAGE ========================================
function assetAToken() external view returns (ERC20);
function assetDecimals() external view returns (uint8);
function totalBalance() external view returns (uint256);
// ========================================= ACCRUAL CONFIG =========================================
/**
* @notice Emitted when accrual period is changed.
* @param oldPeriod time the period was changed from
* @param newPeriod time the period was changed to
*/
event AccrualPeriodChanged(uint32 oldPeriod, uint32 newPeriod);
function accrualPeriod() external view returns (uint32);
function lastAccrual() external view returns (uint64);
function maxLocked() external view returns (uint160);
function setAccrualPeriod(uint32 newAccrualPeriod) external;
// =========================================== 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);
function platformFee() external view returns (uint64);
function performanceFee() external view returns (uint64);
function feesDistributor() external view returns (bytes32);
function setFeesDistributor(bytes32 newFeesDistributor) external;
// ======================================== TRUST CONFIG ========================================
/**
* @notice Emitted when trust for a position is changed.
* @param position address of the position that trust was changed for
* @param trusted whether the position was trusted or untrusted
*/
event TrustChanged(address indexed position, bool trusted);
function isTrusted(ERC20) external view returns (bool);
function setTrust(ERC20 position, bool trust) external;
// ======================================== 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);
function liquidityLimit() external view returns (uint256);
function depositLimit() external view returns (uint256);
function setLiquidityLimit(uint256 newLimit) external;
function setDepositLimit(uint256 newLimit) external;
// ======================================== EMERGENCY LOGIC ========================================
/**
* @notice Emitted when cellar is shutdown.
* @param emptyPositions whether the current position(s) was exited
*/
event ShutdownInitiated(bool emptyPositions);
/**
* @notice Emitted when shutdown is lifted.
*/
event ShutdownLifted();
function isShutdown() external view returns (bool);
function initiateShutdown(bool emptyPosition) external;
function liftShutdown() external;
// ========================================== IMMUTABLES ==========================================
function curveRegistryExchange() external view returns (ICurveSwaps);
function sushiswapRouter() external view returns (ISushiSwapRouter);
function lendingPool() external view returns (ILendingPool);
function incentivesController() external view returns (IAaveIncentivesController);
function gravityBridge() external view returns (IGravity);
function stkAAVE() external view returns (IStakedTokenV2);
function AAVE() external view returns (ERC20);
function WETH() external view returns (ERC20);
// ======================================= ACCOUNTING LOGIC =======================================
function totalHoldings() external view returns (uint256);
function totalLocked() external view returns (uint256);
// ======================================== ACCRUAL LOGIC ========================================
/**
* @notice Emitted on accruals.
* @param platformFees amount of shares minted as platform fees this accrual
* @param performanceFees amount of shares minted as performance fees this accrual
* @param yield amount of assets accrued as yield that will be distributed over this accrual period
*/
event Accrual(uint256 platformFees, uint256 performanceFees, uint256 yield);
/**
* @notice Accrue yield, platform fees, and performance fees.
* @dev Since this is the function responsible for distributing yield to shareholders and
* updating the cellar's balance, it is important to make sure it gets called regularly.
*/
function accrue() external;
// ========================================= POSITION LOGIC =========================================
/**
* @notice Emitted on deposit to Aave.
* @param position the address of the position
* @param assets the amount of assets to deposit
*/
event DepositIntoPosition(address indexed position, uint256 assets);
/**
* @notice Emitted on withdraw from Aave.
* @param position the address of the position
* @param assets the amount of assets to withdraw
*/
event WithdrawFromPosition(address indexed position, uint256 assets);
/**
* @notice Emitted upon entering assets into the current position on Aave.
* @param position the address of the asset being pushed into the current position
* @param assets amount of assets being pushed
*/
event EnterPosition(address indexed position, uint256 assets);
/**
* @notice Emitted upon exiting assets from the current position on Aave.
* @param position the address of the asset being pulled from the current position
* @param assets amount of assets being pulled
*/
event ExitPosition(address indexed position, uint256 assets);
/**
* @notice Emitted on rebalance of Aave poisition.
* @param oldAsset the address of the asset for the old position
* @param newAsset the address of the asset for the new position
* @param assets the amount of the new assets cellar has after rebalancing
*/
event Rebalance(address indexed oldAsset, address indexed newAsset, uint256 assets);
function enterPosition() external;
function enterPosition(uint256 assets) external;
function exitPosition() external;
function exitPosition(uint256 assets) external;
function rebalance(
address[9] memory route,
uint256[3][4] memory swapParams,
uint256 minAssetsOut
) external;
// ========================================= REINVEST LOGIC =========================================
/**
* @notice Emitted upon claiming rewards and beginning cooldown period to unstake them.
* @param rewards amount of rewards that were claimed
*/
event ClaimAndUnstake(uint256 rewards);
/**
* @notice Emitted upon reinvesting rewards into the current position.
* @param token the address of the asset rewards were swapped to
* @param rewards amount of rewards swapped to be reinvested
* @param assets amount of assets received from swapping rewards
*/
event Reinvest(address indexed token, uint256 rewards, uint256 assets);
function claimAndUnstake() external returns (uint256 rewards);
function reinvest(uint256 minAssetsOut) external;
// =========================================== FEES LOGIC ===========================================
/**
* @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);
function sendFees() external;
// ========================================= RECOVERY LOGIC =========================================
/**
* @notice Emitted when tokens accidentally sent to cellar are recovered.
* @param token the address of the token
* @param to the address sweeped tokens were transferred to
* @param amount amount transferred out
*/
event Sweep(address indexed token, address indexed to, uint256 amount);
function sweep(ERC20 token, address to) external;
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
/**
* @notice Partial interface for a Curve Registry Exchanges contract
* @dev The registry exchange contract is used to find pools and query exchange rates for token swaps.
* It also provides a unified exchange API that can be useful for on-chain integrators.
**/
interface ICurveSwaps {
/**
* @notice Perform up to four swaps in a single transaction
* @dev Routing and swap params must be determined off-chain. This
* functionality is designed for gas efficiency over ease-of-use.
* @param _route Array of [initial token, pool, token, pool, token, ...]
* The array is iterated until a pool address of 0x00, then the last
* given token is transferred to `_receiver` (address to transfer the final output token to)
* @param _swap_params Multidimensional array of [i, j, swap type] where i and j are the correct
* values for the n'th pool in `_route`. The swap type should be 1 for
* a stableswap `exchange`, 2 for stableswap `exchange_underlying`, 3
* for a cryptoswap `exchange`, 4 for a cryptoswap `exchange_underlying`
* and 5 for Polygon factory metapools `exchange_underlying`
* @param _expected The minimum amount received after the final swap.
* @return Received amount of final output token
**/
function exchange_multiple(
address[9] memory _route,
uint256[3][4] memory _swap_params,
uint256 _amount,
uint256 _expected
) external returns (uint256);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
interface IGravity {
function sendToCosmos(
address _tokenContract,
bytes32 _destination,
uint256 _amount
) external;
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
/**
* @dev Partial interface for a Aave LendingPool contract,
* which is the main point of interaction with an Aave protocol's market
**/
interface ILendingPool {
/**
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
* @param asset The address of the underlying asset to deposit
* @param amount The amount to be deposited
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to Address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
* @return The final amount withdrawn
**/
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
/**
* @dev Returns the normalized income normalized income of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The reserve's normalized income
*/
function getReserveNormalizedIncome(address asset) external view returns (uint256);
/**
* @dev Returns the normalized income normalized income of the reserve
* @param asset The address of the underlying asset of the reserve
**/
function getReserveData(address asset)
external
view
returns (
//stores the reserve configuration
//bit 0-15: LTV
//bit 16-31: Liq. threshold
//bit 32-47: Liq. bonus
//bit 48-55: Decimals
//bit 56: Reserve is active
//bit 57: reserve is frozen
//bit 58: borrowing is enabled
//bit 59: stable rate borrowing enabled
//bit 60-63: reserved
//bit 64-79: reserve factor
uint256 configuration,
//the liquidity index. Expressed in ray
uint128 liquidityIndex,
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex,
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate,
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate,
//the current stable borrow rate. Expressed in ray
uint128 currentStableBorrowRate,
uint40 lastUpdateTimestamp,
//tokens addresses
address aTokenAddress,
address stableDebtTokenAddress,
address variableDebtTokenAddress,
//address of the interest rate strategy
address interestRateStrategyAddress,
//the id of the reserve. Represents the position in the list of the active reserves
uint8 id
);
}
// 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: agpl-3.0
pragma solidity 0.8.15;
interface IStakedTokenV2 {
function stake(address to, uint256 amount) external;
function redeem(address to, uint256 amount) external;
function cooldown() external;
function claimRewards(address to, uint256 amount) external;
function balanceOf(address account) external view returns (uint256);
function stakersCooldowns(address account) external view returns (uint256);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
/**
* @notice Partial interface for a SushiSwap Router contract
**/
interface ISushiSwapRouter {
/**
* @notice Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the `path`
* @dev The first element of `path` is the input token, the last is the output token,
* and any intermediate elements represent intermediate pairs to trade through (if, for example, a direct pair does not exist).
* `msg.sender` should have already given the router an allowance of at least `amountIn` on the input token
* @param amountIn The amount of input tokens to send
* @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert
* @param path An array of token addresses. `path.length` must be >= 2. Pools for each consecutive pair of addresses must exist and have liquidity
* @param to Recipient of the output tokens
* @param deadline Unix timestamp after which the transaction will revert
* @return amounts The input token amount and all subsequent output token amounts
**/
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;
library Math {
/**
* @notice Substract and return 0 instead if results are negative.
*/
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 "../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;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
{
"compilationTarget": {
"src/AaveV2StablecoinCellar.sol": "AaveV2StablecoinCellar"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"contract ERC20","name":"_asset","type":"address"},{"internalType":"contract ERC20[]","name":"_approvedPositions","type":"address[]"},{"internalType":"contract ICurveSwaps","name":"_curveRegistryExchange","type":"address"},{"internalType":"contract ISushiSwapRouter","name":"_sushiswapRouter","type":"address"},{"internalType":"contract ILendingPool","name":"_lendingPool","type":"address"},{"internalType":"contract IAaveIncentivesController","name":"_incentivesController","type":"address"},{"internalType":"contract IGravity","name":"_gravityBridge","type":"address"},{"internalType":"contract IStakedTokenV2","name":"_stkAAVE","type":"address"},{"internalType":"contract ERC20","name":"_AAVE","type":"address"},{"internalType":"contract ERC20","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"STATE_AccrualOngoing","type":"error"},{"inputs":[{"internalType":"address","name":"assetWithFeeOnTransfer","type":"address"}],"name":"STATE_AssetUsesFeeOnTransfer","type":"error"},{"inputs":[],"name":"STATE_ContractShutdown","type":"error"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"uint256","name":"maxDeposit","type":"uint256"}],"name":"USR_DepositRestricted","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"USR_ProtectedAsset","type":"error"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"USR_SamePosition","type":"error"},{"inputs":[{"internalType":"uint8","name":"newDecimals","type":"uint8"},{"internalType":"uint8","name":"maxDecimals","type":"uint8"}],"name":"USR_TooManyDecimals","type":"error"},{"inputs":[{"internalType":"address","name":"unsupportedPosition","type":"address"}],"name":"USR_UnsupportedPosition","type":"error"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"USR_UntrustedPosition","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"platformFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"performanceFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"yield","type":"uint256"}],"name":"Accrual","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"oldPeriod","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"newPeriod","type":"uint32"}],"name":"AccrualPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rewards","type":"uint256"}],"name":"ClaimAndUnstake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"DepositIntoPosition","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":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"EnterPosition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"ExitPosition","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":"oldLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"LiquidityLimitChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","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":"oldAsset","type":"address"},{"indexed":true,"internalType":"address","name":"newAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"Rebalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"rewards","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"Reinvest","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":"bool","name":"emptyPositions","type":"bool"}],"name":"ShutdownInitiated","type":"event"},{"anonymous":false,"inputs":[],"name":"ShutdownLifted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sweep","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"bool","name":"trusted","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":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"WithdrawFromPosition","type":"event"},{"inputs":[],"name":"AAVE","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accrualPeriod","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accrue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"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":[],"name":"assetAToken","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetDecimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimAndUnstake","outputs":[{"internalType":"uint256","name":"rewards","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"curveRegistryExchange","outputs":[{"internalType":"contract ICurveSwaps","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"enterPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"enterPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"exitPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feesDistributor","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gravityBridge","outputs":[{"internalType":"contract IGravity","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"highWatermarkBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incentivesController","outputs":[{"internalType":"contract IAaveIncentivesController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"emptyPosition","type":"bool"}],"name":"initiateShutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isShutdown","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","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":"lendingPool","outputs":[{"internalType":"contract ILendingPool","name":"","type":"address"}],"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":[],"name":"maxLocked","outputs":[{"internalType":"uint160","name":"","type":"uint160"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"performanceFee","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"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":[],"name":"platformFee","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[9]","name":"route","type":"address[9]"},{"internalType":"uint256[3][4]","name":"swapParams","type":"uint256[3][4]"},{"internalType":"uint256","name":"minAssetsOut","type":"uint256"}],"name":"rebalance","outputs":[],"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":[{"internalType":"uint256","name":"minAssetsOut","type":"uint256"}],"name":"reinvest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sendFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"newAccrualPeriod","type":"uint32"}],"name":"setAccrualPeriod","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":"uint256","name":"newLimit","type":"uint256"}],"name":"setLiquidityLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"position","type":"address"},{"internalType":"bool","name":"trust","type":"bool"}],"name":"setTrust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stkAAVE","outputs":[{"internalType":"contract IStakedTokenV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sushiswapRouter","outputs":[{"internalType":"contract ISushiSwapRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"sweep","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":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalHoldings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalLocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]