Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
AutopoolFees
Compiler Version
v0.8.17+commit.8df45f5f
Optimization Enabled:
Yes with 200 runs
Other Settings:
london EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { AutopoolToken } from "src/vault/libs/AutopoolToken.sol";
library AutopoolFees {
using Math for uint256;
using AutopoolToken for AutopoolToken.TokenData;
/// @notice Profit denomination
uint256 public constant MAX_BPS_PROFIT = 1_000_000_000;
/// @notice 100% == 10000
uint256 public constant FEE_DIVISOR = 10_000;
/// @notice Max periodic fee, 10%. 100% = 10_000.
uint256 public constant MAX_PERIODIC_FEE_BPS = 1000;
uint256 public constant SECONDS_IN_YEAR = 365 * 1 days;
event FeeCollected(uint256 fees, address feeSink, uint256 mintedShares, uint256 profit, uint256 totalAssets);
event PeriodicFeeCollected(uint256 fees, address feeSink, uint256 mintedShares);
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event PeriodicFeeSet(uint256 newFee);
event PeriodicFeeSinkSet(address newPeriodicFeeSink);
event LastPeriodicFeeTakeSet(uint256 lastPeriodicFeeTake);
event RebalanceFeeHighWaterMarkEnabledSet(bool enabled);
event NewNavShareFeeMark(uint256 navPerShare, uint256 timestamp);
event NewTotalAssetsHighWatermark(uint256 assets, uint256 timestamp);
event StreamingFeeSet(uint256 newFee);
event FeeSinkSet(address newFeeSink);
event NewProfitUnlockTime(uint48 timeSeconds);
error InvalidFee(uint256 newFee);
error AlreadySet();
/// @notice Returns the amount of unlocked profit shares that will be burned
function unlockedShares(
IAutopool.ProfitUnlockSettings storage profitUnlockSettings,
AutopoolToken.TokenData storage tokenData
) public view returns (uint256 shares) {
uint256 fullTime = profitUnlockSettings.fullProfitUnlockTime;
if (fullTime > block.timestamp) {
shares = profitUnlockSettings.profitUnlockRate
* (block.timestamp - profitUnlockSettings.lastProfitUnlockTime) / MAX_BPS_PROFIT;
} else if (fullTime != 0) {
shares = tokenData.balances[address(this)];
}
}
function initializeFeeSettings(IAutopool.AutopoolFeeSettings storage settings) external {
uint256 timestamp = block.timestamp;
settings.lastPeriodicFeeTake = timestamp; // Stops fees from being able to be claimed before init timestamp.
settings.navPerShareLastFeeMark = FEE_DIVISOR;
settings.navPerShareLastFeeMarkTimestamp = timestamp;
emit LastPeriodicFeeTakeSet(timestamp);
}
function burnUnlockedShares(
IAutopool.ProfitUnlockSettings storage profitUnlockSettings,
AutopoolToken.TokenData storage tokenData
) external {
uint256 shares = unlockedShares(profitUnlockSettings, tokenData);
if (shares == 0) {
return;
}
if (profitUnlockSettings.fullProfitUnlockTime > block.timestamp) {
profitUnlockSettings.lastProfitUnlockTime = uint48(block.timestamp);
}
tokenData.burn(address(this), shares);
}
function _calculateEffectiveNavPerShareLastFeeMark(
IAutopool.AutopoolFeeSettings storage settings,
uint256 currentBlock,
uint256 currentNavPerShare,
uint256 aumCurrent
) private view returns (uint256) {
uint256 workingHigh = settings.navPerShareLastFeeMark;
if (workingHigh == 0) {
// If we got 0, we shouldn't increase it
return 0;
}
if (!settings.rebalanceFeeHighWaterMarkEnabled) {
// No calculations or checks to do in this case
return workingHigh;
}
uint256 daysSinceLastFeeEarned = (currentBlock - settings.navPerShareLastFeeMarkTimestamp) / 60 / 60 / 24;
if (daysSinceLastFeeEarned > 600) {
return currentNavPerShare;
}
if (daysSinceLastFeeEarned > 60 && daysSinceLastFeeEarned <= 600) {
uint8 decimals = IAutopool(address(this)).decimals();
uint256 one = 10 ** decimals;
uint256 aumHighMark = settings.totalAssetsHighMark;
// AUM_min = min(AUM_high, AUM_current)
uint256 minAssets = aumCurrent < aumHighMark ? aumCurrent : aumHighMark;
// AUM_max = max(AUM_high, AUM_current);
uint256 maxAssets = aumCurrent > aumHighMark ? aumCurrent : aumHighMark;
/// 0.999 * (AUM_min / AUM_max)
// dividing by `one` because we need end up with a number in the 100's wei range
uint256 g1 = ((999 * minAssets * one) / (maxAssets * one));
/// 0.99 * (1 - AUM_min / AUM_max)
// dividing by `10 ** (decimals() - 1)` because we need to divide 100 out for our % and then
// we want to end up with a number in the 10's wei range
uint256 g2 = (99 * (one - (minAssets * one / maxAssets))) / 10 ** (decimals - 1);
uint256 gamma = g1 + g2;
uint256 daysDiff = daysSinceLastFeeEarned - 60;
for (uint256 i = 0; i < daysDiff / 25; ++i) {
// slither-disable-next-line divide-before-multiply
workingHigh = workingHigh * (gamma ** 25 / 1e72) / 1000;
}
// slither-disable-next-line weak-prng
for (uint256 i = 0; i < daysDiff % 25; ++i) {
// slither-disable-next-line divide-before-multiply
workingHigh = workingHigh * gamma / 1000;
}
}
return workingHigh;
}
function collectFees(
uint256 totalAssets,
uint256 currentTotalSupply,
IAutopool.AutopoolFeeSettings storage settings,
AutopoolToken.TokenData storage tokenData,
bool collectPeriodicFees
) external returns (uint256) {
// If there's no supply then there should be no assets and so nothing
// to actually take fees on
// slither-disable-next-line incorrect-equality
if (currentTotalSupply == 0) {
return 0;
}
// slither-disable-next-line incorrect-equality
if (settings.totalAssetsHighMark == 0) {
// Initialize our high water mark to the current assets
settings.totalAssetsHighMark = totalAssets;
}
// slither-disable-start timestamp
if (collectPeriodicFees) {
address periodicFeeSink = settings.periodicFeeSink;
uint256 periodicFeeBps = settings.periodicFeeBps;
// If there is a periodic fee and fee sink set, take the fee.
if (periodicFeeBps > 0 && periodicFeeSink != address(0)) {
uint256 durationSinceLastPeriodicFeeTake = block.timestamp - settings.lastPeriodicFeeTake;
uint256 timeAdjustedBps = durationSinceLastPeriodicFeeTake.mulDiv(
periodicFeeBps * FEE_DIVISOR, SECONDS_IN_YEAR, Math.Rounding.Up
);
uint256 periodicShares =
_collectPeriodicFees(periodicFeeSink, timeAdjustedBps, currentTotalSupply, totalAssets);
currentTotalSupply += periodicShares;
tokenData.mint(periodicFeeSink, periodicShares);
}
// Needs to be kept up to date so if a fee is suddenly turned on a large part of assets do not get
// claimed as fees.
settings.lastPeriodicFeeTake = block.timestamp;
emit LastPeriodicFeeTakeSet(block.timestamp);
}
// slither-disable-end timestamp
uint256 currentNavPerShare = (totalAssets * FEE_DIVISOR) / currentTotalSupply;
// If the high mark is disabled then this just returns the `navPerShareLastFeeMark`
// Otherwise, it'll check if it needs to decay
uint256 effectiveNavPerShareLastFeeMark =
_calculateEffectiveNavPerShareLastFeeMark(settings, block.timestamp, currentNavPerShare, totalAssets);
if (currentNavPerShare > effectiveNavPerShareLastFeeMark) {
// Even if we aren't going to take the fee (haven't set a sink)
// We still want to calculate so we can emit for off-chain analysis
uint256 profit = (currentNavPerShare - effectiveNavPerShareLastFeeMark) * currentTotalSupply;
uint256 fees = profit.mulDiv(settings.streamingFeeBps, (FEE_DIVISOR ** 2), Math.Rounding.Up);
if (fees > 0) {
currentTotalSupply = _mintStreamingFee(
tokenData, fees, settings.streamingFeeBps, profit, currentTotalSupply, totalAssets, settings.feeSink
);
currentNavPerShare = (totalAssets * FEE_DIVISOR) / currentTotalSupply;
}
}
// Two situations we're covering here
// 1. If the high mark is disabled then we just always need to know the last
// time we evaluated fees so we can catch any run up. i.e. the `navPerShareLastFeeMark`
// can go down
// 2. When the high mark is enabled, then we only want to set `navPerShareLastFeeMark`
// when it is greater than the last time we captured fees (or would have)
if (currentNavPerShare >= effectiveNavPerShareLastFeeMark || !settings.rebalanceFeeHighWaterMarkEnabled) {
settings.navPerShareLastFeeMark = currentNavPerShare;
settings.navPerShareLastFeeMarkTimestamp = block.timestamp;
emit NewNavShareFeeMark(currentNavPerShare, block.timestamp);
}
// Set our new high water mark for totalAssets, regardless if we took fees
if (settings.totalAssetsHighMark < totalAssets) {
settings.totalAssetsHighMark = totalAssets;
settings.totalAssetsHighMarkTimestamp = block.timestamp;
emit NewTotalAssetsHighWatermark(settings.totalAssetsHighMark, settings.totalAssetsHighMarkTimestamp);
}
return currentTotalSupply;
}
function _mintStreamingFee(
AutopoolToken.TokenData storage tokenData,
uint256 fees,
uint256 streamingFeeBps,
uint256 profit,
uint256 currentTotalSupply,
uint256 totalAssets,
address sink
) private returns (uint256) {
if (sink == address(0)) {
return currentTotalSupply;
}
uint256 streamingFeeShares =
_calculateSharesToMintFeeCollection(streamingFeeBps, totalAssets, currentTotalSupply);
tokenData.mint(sink, streamingFeeShares);
currentTotalSupply += streamingFeeShares;
emit Deposit(address(this), sink, 0, streamingFeeShares);
emit FeeCollected(fees, sink, streamingFeeShares, profit, totalAssets);
return currentTotalSupply;
}
/// @dev Collects periodic fees.
function _collectPeriodicFees(
address periodicSink,
uint256 timeAdjustedFeeBps,
uint256 currentTotalSupply,
uint256 totalAssets
) private returns (uint256 newShares) {
newShares = _calculateSharesToMintFeeCollection(timeAdjustedFeeBps, totalAssets, currentTotalSupply);
// Fee in assets that we are taking.
uint256 fees = (timeAdjustedFeeBps * totalAssets / FEE_DIVISOR).ceilDiv(FEE_DIVISOR);
emit Deposit(address(this), periodicSink, 0, newShares);
emit PeriodicFeeCollected(fees, periodicSink, newShares);
return newShares;
}
function _calculateSharesToMintFeeCollection(
uint256 feeBps,
uint256 totalAssets,
uint256 totalSupply
) private pure returns (uint256 toMint) {
// Gas savings, this is used twice.
uint256 feeTotalAssets = feeBps * totalAssets / FEE_DIVISOR;
// Calculated separate from other mints as normal share mint is round down
// Note: We use Lido's formula: from https://docs.lido.fi/guides/lido-tokens-integration-guide/#fees
// suggested by: https://github.com/sherlock-audit/2023-06-tokemak-judging/blob/main/486-H/624-best.md
// but we scale down `profit` by FEE_DIVISOR
toMint =
Math.mulDiv(feeTotalAssets, totalSupply, (totalAssets * FEE_DIVISOR) - (feeTotalAssets), Math.Rounding.Up);
}
/// @dev If set to 0, existing shares will unlock immediately and increase nav/share. This is intentional
function setProfitUnlockPeriod(
IAutopool.ProfitUnlockSettings storage settings,
AutopoolToken.TokenData storage tokenData,
uint48 newUnlockPeriodInSeconds
) external {
settings.unlockPeriodInSeconds = newUnlockPeriodInSeconds;
// If we are turning off the unlock, setting it to 0, then
// unlock all existing shares
if (newUnlockPeriodInSeconds == 0) {
uint256 currentShares = tokenData.balances[address(this)];
if (currentShares > 0) {
settings.lastProfitUnlockTime = uint48(block.timestamp);
tokenData.burn(address(this), currentShares);
}
// Reset vars so old values aren't used during a subsequent lockup
settings.fullProfitUnlockTime = 0;
settings.profitUnlockRate = 0;
}
emit NewProfitUnlockTime(newUnlockPeriodInSeconds);
}
function calculateProfitLocking(
IAutopool.ProfitUnlockSettings storage settings,
AutopoolToken.TokenData storage tokenData,
uint256 feeShares,
uint256 newTotalAssets,
uint256 startTotalAssets,
uint256 startTotalSupply,
uint256 previousLockShares
) external returns (uint256) {
uint256 unlockPeriod = settings.unlockPeriodInSeconds;
// If there were existing shares and we set the unlock period to 0 they are immediately unlocked
// so we don't have to worry about existing shares here. And if the period is 0 then we
// won't be locking any new shares
if (unlockPeriod == 0 || startTotalAssets == 0) {
return startTotalSupply;
}
uint256 newLockShares = 0;
uint256 previousLockToBurn = 0;
uint256 effectiveTs = startTotalSupply;
// The total supply we would need to not see a change in nav/share
uint256 targetTotalSupply = newTotalAssets * (effectiveTs - feeShares) / startTotalAssets;
if (effectiveTs > targetTotalSupply) {
// Our actual total supply is greater than our target.
// This means we would see a decrease in nav/share
// See if we can burn any profit shares to offset that
if (previousLockShares > 0) {
uint256 diff = effectiveTs - targetTotalSupply;
if (previousLockShares >= diff) {
previousLockToBurn = diff;
effectiveTs -= diff;
} else {
previousLockToBurn = previousLockShares;
effectiveTs -= previousLockShares;
}
}
}
if (targetTotalSupply > effectiveTs) {
// Our actual total supply is less than our target.
// This means we would see an increase in nav/share (due to gains) which we can't allow
// We need to mint shares to the vault to offset
newLockShares = targetTotalSupply - effectiveTs;
effectiveTs += newLockShares;
}
// We know how many shares should be locked at this point
// Mint or burn what we need to match if necessary
uint256 totalLockShares = previousLockShares - previousLockToBurn + newLockShares;
if (totalLockShares > previousLockShares) {
uint256 mintAmount = totalLockShares - previousLockShares;
tokenData.mint(address(this), mintAmount);
startTotalSupply += mintAmount;
} else if (totalLockShares < previousLockShares) {
uint256 burnAmount = previousLockShares - totalLockShares;
tokenData.burn(address(this), burnAmount);
startTotalSupply -= burnAmount;
}
// If we're going to end up with no profit shares, zero the rate
// We don't need to 0 the other timing vars if we just zero the rate
if (totalLockShares == 0) {
settings.profitUnlockRate = 0;
}
// We have shares and they are going to unlocked later
if (totalLockShares > 0 && unlockPeriod > 0) {
_updateProfitUnlockTimings(
settings, unlockPeriod, previousLockToBurn, previousLockShares, newLockShares, totalLockShares
);
}
return startTotalSupply;
}
function _updateProfitUnlockTimings(
IAutopool.ProfitUnlockSettings storage settings,
uint256 unlockPeriod,
uint256 previousLockToBurn,
uint256 previousLockShares,
uint256 newLockShares,
uint256 totalLockShares
) private {
uint256 previousLockTime;
uint256 fullUnlockTime = settings.fullProfitUnlockTime;
// Determine how much time is left for the remaining previous profit shares
if (fullUnlockTime > block.timestamp) {
previousLockTime = (previousLockShares - previousLockToBurn) * (fullUnlockTime - block.timestamp);
}
// Amount of time it will take to unlock all shares, weighted avg over current and new shares
uint256 newUnlockPeriod = (previousLockTime + newLockShares * unlockPeriod) / totalLockShares;
// Rate at which totalLockShares will unlock
settings.profitUnlockRate = totalLockShares * MAX_BPS_PROFIT / newUnlockPeriod;
// Time the full of amount of totalLockShares will be unlocked
settings.fullProfitUnlockTime = uint48(block.timestamp + newUnlockPeriod);
settings.lastProfitUnlockTime = uint48(block.timestamp);
}
/// @notice Enable or disable the high water mark on the rebalance fee
/// @dev Will revert if set to the same value
function setRebalanceFeeHighWaterMarkEnabled(
IAutopool.AutopoolFeeSettings storage feeSettings,
bool enabled
) external {
if (feeSettings.rebalanceFeeHighWaterMarkEnabled == enabled) {
revert AlreadySet();
}
feeSettings.rebalanceFeeHighWaterMarkEnabled = enabled;
emit RebalanceFeeHighWaterMarkEnabledSet(enabled);
}
/// @notice Set the fee that will be taken when profit is realized
/// @dev Resets the high water to current value
/// @param fee Percent. 100% == 10000
function setStreamingFeeBps(IAutopool.AutopoolFeeSettings storage feeSettings, uint256 fee) external {
if (fee >= FEE_DIVISOR) {
revert InvalidFee(fee);
}
feeSettings.streamingFeeBps = fee;
IAutopool vault = IAutopool(address(this));
// Set the high mark when we change the fee so we aren't able to go farther back in
// time than one debt reporting and claim fee's against past profits
uint256 ts = vault.totalSupply();
if (ts > 0) {
uint256 ta = vault.totalAssets();
if (ta > 0) {
feeSettings.navPerShareLastFeeMark = (ta * FEE_DIVISOR) / ts;
} else {
feeSettings.navPerShareLastFeeMark = FEE_DIVISOR;
}
}
emit StreamingFeeSet(fee);
}
/// @notice Set the periodic fee taken.
/// @dev Zero is allowed, no fee taken.
/// @param fee Fee to update periodic fee to.
function setPeriodicFeeBps(IAutopool.AutopoolFeeSettings storage feeSettings, uint256 fee) external {
if (fee > MAX_PERIODIC_FEE_BPS) {
revert InvalidFee(fee);
}
// Fee checked to fit into uint16 above, able to be wrapped without safe cast here.
emit PeriodicFeeSet(fee);
feeSettings.periodicFeeBps = uint16(fee);
}
/// @notice Set the address that will receive fees
/// @param newFeeSink Address that will receive fees
function setFeeSink(IAutopool.AutopoolFeeSettings storage feeSettings, address newFeeSink) external {
emit FeeSinkSet(newFeeSink);
// Zero is valid. One way to disable taking fees
// slither-disable-next-line missing-zero-check
feeSettings.feeSink = newFeeSink;
}
/// @notice Sets the address that will receive periodic fees.
/// @dev Zero address allowable. Disables fees.
/// @param newPeriodicFeeSink New periodic fee address.
function setPeriodicFeeSink(
IAutopool.AutopoolFeeSettings storage feeSettings,
address newPeriodicFeeSink
) external {
emit PeriodicFeeSinkSet(newPeriodicFeeSink);
// slither-disable-next-line missing-zero-check
feeSettings.periodicFeeSink = newPeriodicFeeSink;
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { AutopoolDebt } from "src/vault/libs/AutopoolDebt.sol";
import { IERC4626 } from "src/interfaces/vault/IERC4626.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { IERC20Permit } from "openzeppelin-contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
interface IAutopool is IERC4626, IERC20Permit {
enum VaultShutdownStatus {
Active,
Deprecated,
Exploit
}
/// @param unlockPeriodInSeconds Time it takes for profit to unlock in seconds
/// @param fullProfitUnlockTime Time at which all profit will have been unlocked
/// @param lastProfitUnlockTime Last time profits were unlocked
/// @param profitUnlockRate Per second rate at which profit shares unlocks. Rate when calculated is denominated in
/// MAX_BPS_PROFIT. TODO: Get into uint112
struct ProfitUnlockSettings {
uint48 unlockPeriodInSeconds;
uint48 fullProfitUnlockTime;
uint48 lastProfitUnlockTime;
uint256 profitUnlockRate;
}
/// @param feeSink Where claimed fees are sent
/// @param totalAssetsHighMark The last totalAssets amount we took fees at
/// @param totalAssetsHighMarkTimestamp The last timestamp we updated the high water mark
/// @param lastPeriodicFeeTake Timestamp of when the last periodic fee was taken.
/// @param periodicFeeSink Address that receives periodic fee.
/// @param periodicFeeBps Current periodic fee. 100% == 10000.
/// @param streamingFeeBps Current streaming fee taken on profit. 100% == 10000
/// @param navPerShareLastFeeMark The last nav/share height we took fees at
/// @param navPerShareLastFeeMarkTimestamp The last timestamp we took fees at
/// @param rebalanceFeeHighWaterMarkEnabled Returns whether the nav/share high water mark is enabled for the
/// rebalance fee
struct AutopoolFeeSettings {
address feeSink;
uint256 totalAssetsHighMark;
uint256 totalAssetsHighMarkTimestamp;
uint256 lastPeriodicFeeTake;
address periodicFeeSink;
uint256 periodicFeeBps;
uint256 streamingFeeBps;
uint256 navPerShareLastFeeMark;
uint256 navPerShareLastFeeMarkTimestamp;
bool rebalanceFeeHighWaterMarkEnabled;
}
/// @param totalIdle The amount of baseAsset deposited into the contract pending deployment
/// @param totalDebt The current (though cached) value of assets we've deployed
/// @param totalDebtMin The current (though cached) value of assets we use for valuing during deposits
/// @param totalDebtMax The current (though cached) value of assets we use for valuing during withdrawals
struct AssetBreakdown {
uint256 totalIdle;
uint256 totalDebt;
uint256 totalDebtMin;
uint256 totalDebtMax;
}
enum TotalAssetPurpose {
Global,
Deposit,
Withdraw
}
/* ******************************** */
/* Events */
/* ******************************** */
event TokensPulled(address[] tokens, uint256[] amounts, address[] destinations);
event TokensRecovered(address[] tokens, uint256[] amounts, address[] destinations);
event Nav(uint256 idle, uint256 debt, uint256 totalSupply);
event RewarderSet(address newRewarder, address oldRewarder);
event DestinationDebtReporting(address destination, uint256 debtValue, uint256 claimed, uint256 claimGasUsed);
event FeeCollected(uint256 fees, address feeSink, uint256 mintedShares, uint256 profit, uint256 idle, uint256 debt);
event PeriodicFeeCollected(uint256 fees, address feeSink, uint256 mintedShares);
event Shutdown(VaultShutdownStatus reason);
/* ******************************** */
/* Errors */
/* ******************************** */
error ERC4626MintExceedsMax(uint256 shares, uint256 maxMint);
error ERC4626DepositExceedsMax(uint256 assets, uint256 maxDeposit);
error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);
error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);
error InvalidShutdownStatus(VaultShutdownStatus status);
error WithdrawalFailed();
error DepositFailed();
error InsufficientFundsInDestinations(uint256 deficit);
error WithdrawalIncomplete();
error ValueSharesMismatch(uint256 value, uint256 shares);
/// @notice A full unit of this pool
// solhint-disable-next-line func-name-mixedcase
function ONE() external view returns (uint256);
/// @notice Query the type of vault
function vaultType() external view returns (bytes32);
/// @notice Strategy governing the pools rebalances
function autoPoolStrategy() external view returns (IAutopoolStrategy);
/// @notice Allow token recoverer to collect dust / unintended transfers (non-tracked assets only)
function recover(address[] calldata tokens, uint256[] calldata amounts, address[] calldata destinations) external;
/// @notice Set the order of destination vaults used for withdrawals
// NOTE: will be done going directly to strategy (IStrategy) vault points to.
// How it'll delegate is still being decided
// function setWithdrawalQueue(address[] calldata destinations) external;
/// @notice Get a list of destination vaults with pending assets to clear out
function getRemovalQueue() external view returns (address[] memory);
function getFeeSettings() external view returns (AutopoolFeeSettings memory);
/// @notice Initiate the shutdown procedures for this vault
function shutdown(VaultShutdownStatus reason) external;
/// @notice True if the vault has been shutdown
function isShutdown() external view returns (bool);
/// @notice Returns the reason for shutdown (or `Active` if not shutdown)
function shutdownStatus() external view returns (VaultShutdownStatus);
/// @notice gets the list of supported destination vaults for the Autopool/Strategy
/// @return _destinations List of supported destination vaults
function getDestinations() external view returns (address[] memory _destinations);
function convertToShares(
uint256 assets,
uint256 totalAssetsForPurpose,
uint256 supply,
Math.Rounding rounding
) external view returns (uint256 shares);
function convertToAssets(
uint256 shares,
uint256 totalAssetsForPurpose,
uint256 supply,
Math.Rounding rounding
) external view returns (uint256 assets);
function totalAssets(TotalAssetPurpose purpose) external view returns (uint256);
function getAssetBreakdown() external view returns (AssetBreakdown memory);
/// @notice get a destinations last reported debt value
/// @param destVault the address of the target destination
/// @return destinations last reported debt value
function getDestinationInfo(address destVault) external view returns (AutopoolDebt.DestinationInfo memory);
/// @notice check if a destination is registered with the vault
function isDestinationRegistered(address destination) external view returns (bool);
/// @notice get if a destinationVault is queued for removal by the AutopoolETH
function isDestinationQueuedForRemoval(address destination) external view returns (bool);
/// @notice Returns instance of vault rewarder.
function rewarder() external view returns (IMainRewarder);
/// @notice Returns all past rewarders.
function getPastRewarders() external view returns (address[] memory _pastRewarders);
/// @notice Returns boolean telling whether address passed in is past rewarder.
function isPastRewarder(address _pastRewarder) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { ECDSA } from "openzeppelin-contracts/utils/cryptography/ECDSA.sol";
import { IERC20Permit } from "openzeppelin-contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
/// @notice ERC20 token functionality converted into a library. Based on OZ's v5
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.1/contracts/token/ERC20/ERC20.sol
library AutopoolToken {
struct TokenData {
/// @notice Token balances
/// @dev account => balance
mapping(address => uint256) balances;
/// @notice Account spender allowances
/// @dev account => spender => allowance
mapping(address => mapping(address => uint256)) allowances;
/// @notice Total supply of the pool. Be careful when using this directly from the struct. The pool itself
/// modifies this number based on unlocked profited shares
uint256 totalSupply;
/// @notice ERC20 Permit nonces
/// @dev account -> nonce. Exposed via `nonces(owner)`
mapping(address => uint256) nonces;
}
/// @notice EIP2612 permit type hash
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/// @notice EIP712 domain type hash
bytes32 public constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
/// @param sender Address whose tokens are being transferred.
/// @param balance Current balance for the interacting account.
/// @param needed Minimum amount required to perform a transfer.
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/// @dev Indicates a failure with the token `sender`. Used in transfers.
/// @param sender Address whose tokens are being transferred.
error ERC20InvalidSender(address sender);
/// @dev Indicates a failure with the token `receiver`. Used in transfers.
/// @param receiver Address to which tokens are being transferred.
error ERC20InvalidReceiver(address receiver);
/// @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
///@param spender Address that may be allowed to operate on tokens without being their owner.
/// @param allowance Amount of tokens a `spender` is allowed to operate with.
///@param needed Minimum amount required to perform a transfer.
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/// @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
/// @param approver Address initiating an approval operation.
error ERC20InvalidApprover(address approver);
/// @dev Indicates a failure with the `spender` to be approved. Used in approvals.
/// @param spender Address that may be allowed to operate on tokens without being their owner.
error ERC20InvalidSpender(address spender);
/// @dev Permit deadline has expired.
error ERC2612ExpiredSignature(uint256 deadline);
/// @dev Mismatched signature.
error ERC2612InvalidSigner(address signer, address owner);
/// @dev The nonce used for an `account` is not the expected current nonce.
error InvalidAccountNonce(address account, uint256 currentNonce);
/// @dev Emitted when `value` tokens are moved from one account `from` to another `to`.
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}.
/// `value` is the new allowance.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @dev Sets a `value` amount of tokens as the allowance of `spender` over the caller's tokens.
function approve(TokenData storage data, address spender, uint256 value) external returns (bool) {
address owner = msg.sender;
approve(data, owner, spender, value);
return true;
}
/// @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
function approve(TokenData storage data, address owner, address spender, uint256 value) public {
_approve(data, owner, spender, value, true);
}
function transfer(TokenData storage data, address to, uint256 value) external returns (bool) {
address owner = msg.sender;
_transfer(data, owner, to, value);
return true;
}
/// @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism.
/// value` is then deducted from the caller's allowance.
function transferFrom(TokenData storage data, address from, address to, uint256 value) external returns (bool) {
address spender = msg.sender;
_spendAllowance(data, from, spender, value);
_transfer(data, from, to, value);
return true;
}
/// @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
function mint(TokenData storage data, address account, uint256 value) external {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(data, address(0), account, value);
}
/// @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
function burn(TokenData storage data, address account, uint256 value) external {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(data, account, address(0), value);
}
function permit(
TokenData storage data,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}
uint256 nonce;
// For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here. Nonces starts at 0
nonce = data.nonces[owner]++;
}
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline));
bytes32 hash = ECDSA.toTypedDataHash(IERC20Permit(address(this)).DOMAIN_SEPARATOR(), structHash);
address signer = ECDSA.recover(hash, v, r, s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
approve(data, owner, spender, value);
}
/// @dev Moves a `value` amount of tokens from `from` to `to`.
function _transfer(TokenData storage data, address from, address to, uint256 value) private {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(data, from, to, value);
}
/// @dev Updates `owner` s allowance for `spender` based on spent `value`.
function _spendAllowance(TokenData storage data, address owner, address spender, uint256 value) private {
uint256 currentAllowance = data.allowances[owner][spender];
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(data, owner, spender, currentAllowance - value, false);
}
}
}
/// @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
/// (or `to`) is the zero address.
function _update(TokenData storage data, address from, address to, uint256 value) private {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
data.totalSupply += value;
} else {
uint256 fromBalance = data.balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
data.balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
data.totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
data.balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/// @dev Variant of `_approve` with an optional flag to enable or disable the Approval event.
function _approve(TokenData storage data, address owner, address spender, uint256 value, bool emitEvent) private {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
data.allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { Errors } from "src/utils/Errors.sol";
import { LibAdapter } from "src/libs/LibAdapter.sol";
import { IDestinationVault } from "src/interfaces/vault/IDestinationVault.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { EnumerableSet } from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import { IStrategy } from "src/interfaces/strategy/IStrategy.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20Metadata as IERC20 } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC3156FlashBorrower } from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";
import { IAutopoolStrategy } from "src/interfaces/strategy/IAutopoolStrategy.sol";
import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";
import { WithdrawalQueue } from "src/strategy/WithdrawalQueue.sol";
import { IAutopool } from "src/interfaces/vault/IAutopool.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { AutopoolToken } from "src/vault/libs/AutopoolToken.sol";
library AutopoolDebt {
using Math for uint256;
using SafeERC20 for IERC20;
using WithdrawalQueue for StructuredLinkedList.List;
using EnumerableSet for EnumerableSet.AddressSet;
using AutopoolToken for AutopoolToken.TokenData;
/// @notice Max time a cached debt report can be used
uint256 public constant MAX_DEBT_REPORT_AGE_SECONDS = 1 days;
error VaultShutdown();
error WithdrawShareCalcInvalid(uint256 currentShares, uint256 cachedShares);
error RebalanceDestinationsMatch(address destinationVault);
error RebalanceFailed(string message);
error InvalidPrices();
error InvalidTotalAssetPurpose();
error InvalidDestination(address destination);
error TooFewAssets(uint256 requested, uint256 actual);
error SharesAndAssetsReceived(uint256 assets, uint256 shares);
error AmountExceedsAllowance(uint256 shares, uint256 allowed);
event DestinationDebtReporting(
address destination, AutopoolDebt.IdleDebtUpdates debtInfo, uint256 claimed, uint256 claimGasUsed
);
event NewNavShareFeeMark(uint256 navPerShare, uint256 timestamp);
event Nav(uint256 idle, uint256 debt, uint256 totalSupply);
event Withdraw(
address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
);
struct DestinationInfo {
/// @notice Current underlying value at the destination vault
/// @dev Used for calculating totalDebt, mid point of min and max
uint256 cachedDebtValue;
/// @notice Current minimum underlying value at the destination vault
/// @dev Used for calculating totalDebt during withdrawal
uint256 cachedMinDebtValue;
/// @notice Current maximum underlying value at the destination vault
/// @dev Used for calculating totalDebt of the deposit
uint256 cachedMaxDebtValue;
/// @notice Last block timestamp this info was updated
uint256 lastReport;
/// @notice How many shares of the destination vault we owned at last report
uint256 ownedShares;
}
struct IdleDebtUpdates {
bool pricesWereSafe;
uint256 totalIdleDecrease;
uint256 totalIdleIncrease;
uint256 totalDebtIncrease;
uint256 totalDebtDecrease;
uint256 totalMinDebtIncrease;
uint256 totalMinDebtDecrease;
uint256 totalMaxDebtIncrease;
uint256 totalMaxDebtDecrease;
}
struct RebalanceOutParams {
/// Address that will received the withdrawn underlyer
address receiver;
/// The "out" destination vault
address destinationOut;
/// The amount of tokenOut that will be withdrawn
uint256 amountOut;
/// The underlyer for destinationOut
address tokenOut;
IERC20 _baseAsset;
bool _shutdown;
}
/// @dev In memory struct only for managing vars in _withdraw
struct WithdrawInfo {
uint256 currentIdle;
uint256 assetsFromIdle;
uint256 totalAssetsToPull;
uint256 assetsToPull;
uint256 assetsPulled;
uint256 idleIncrease;
uint256 debtDecrease;
uint256 debtMinDecrease;
uint256 debtMaxDecrease;
uint256 totalMinDebt;
uint256 destinationRound;
uint256 lastRoundSlippage;
uint256 expectedAssets;
}
struct FlashRebalanceParams {
uint256 totalIdle;
uint256 totalDebt;
IERC20 baseAsset;
bool shutdown;
}
struct FlashResultInfo {
uint256 tokenInBalanceBefore;
uint256 tokenInBalanceAfter;
bytes32 flashResult;
}
function flashRebalance(
DestinationInfo storage destInfoOut,
DestinationInfo storage destInfoIn,
IERC3156FlashBorrower receiver,
IStrategy.RebalanceParams memory params,
IStrategy.SummaryStats memory destSummaryOut,
IAutopoolStrategy autoPoolStrategy,
FlashRebalanceParams memory flashParams,
bytes calldata data
) external returns (IdleDebtUpdates memory result) {
// Handle decrease (shares going "Out", cashing in shares and sending underlying back to swapper)
// If the tokenOut is _asset we assume they are taking idle
// which is already in the contract
result = _handleRebalanceOut(
AutopoolDebt.RebalanceOutParams({
receiver: address(receiver),
destinationOut: params.destinationOut,
amountOut: params.amountOut,
tokenOut: params.tokenOut,
_baseAsset: flashParams.baseAsset,
_shutdown: flashParams.shutdown
}),
destInfoOut
);
if (!result.pricesWereSafe) {
revert InvalidPrices();
}
// Handle increase (shares coming "In", getting underlying from the swapper and trading for new shares)
if (params.amountIn > 0) {
FlashResultInfo memory flashResultInfo;
// get "before" counts
flashResultInfo.tokenInBalanceBefore = IERC20(params.tokenIn).balanceOf(address(this));
// Give control back to the solver so they can make use of the "out" assets
// and get our "in" asset
flashResultInfo.flashResult = receiver.onFlashLoan(msg.sender, params.tokenIn, params.amountIn, 0, data);
// We assume the solver will send us the assets
flashResultInfo.tokenInBalanceAfter = IERC20(params.tokenIn).balanceOf(address(this));
// Make sure the call was successful and verify we have at least the assets we think
// we were getting
if (
flashResultInfo.flashResult != keccak256("ERC3156FlashBorrower.onFlashLoan")
|| flashResultInfo.tokenInBalanceAfter < flashResultInfo.tokenInBalanceBefore + params.amountIn
) {
revert Errors.FlashLoanFailed(params.tokenIn, params.amountIn);
}
{
// make sure we have a valid path
(bool success, string memory message) = autoPoolStrategy.verifyRebalance(params, destSummaryOut);
if (!success) {
revert RebalanceFailed(message);
}
}
if (params.tokenIn != address(flashParams.baseAsset)) {
IdleDebtUpdates memory inDebtResult = _handleRebalanceIn(
destInfoIn,
IDestinationVault(params.destinationIn),
params.tokenIn,
flashResultInfo.tokenInBalanceAfter
);
if (!inDebtResult.pricesWereSafe) {
revert InvalidPrices();
}
result.totalDebtDecrease += inDebtResult.totalDebtDecrease;
result.totalDebtIncrease += inDebtResult.totalDebtIncrease;
result.totalMinDebtDecrease += inDebtResult.totalMinDebtDecrease;
result.totalMinDebtIncrease += inDebtResult.totalMinDebtIncrease;
result.totalMaxDebtDecrease += inDebtResult.totalMaxDebtDecrease;
result.totalMaxDebtIncrease += inDebtResult.totalMaxDebtIncrease;
} else {
result.totalIdleIncrease += flashResultInfo.tokenInBalanceAfter - flashResultInfo.tokenInBalanceBefore;
}
}
}
/// @notice Perform deposit and debt info update for the "in" destination during a rebalance
/// @dev This "in" function performs less validations than its "out" version
/// @param dvIn The "in" destination vault
/// @param tokenIn The underlyer for dvIn
/// @param depositAmount The amount of tokenIn that will be deposited
/// @return result Changes in debt values
function _handleRebalanceIn(
DestinationInfo storage destInfo,
IDestinationVault dvIn,
address tokenIn,
uint256 depositAmount
) private returns (IdleDebtUpdates memory result) {
LibAdapter._approve(IERC20(tokenIn), address(dvIn), depositAmount);
// Snapshot our current shares so we know how much to back out
uint256 originalShareBal = dvIn.balanceOf(address(this));
// deposit to dv
uint256 newShares = dvIn.depositUnderlying(depositAmount);
// Update the debt info snapshot
result = _recalculateDestInfo(destInfo, dvIn, originalShareBal, originalShareBal + newShares);
}
/**
* @notice Perform withdraw and debt info update for the "out" destination during a rebalance
* @dev This "out" function performs more validations and handles idle as opposed to "in" which does not
* debtDecrease The previous amount of debt destinationOut accounted for in totalDebt
* debtIncrease The current amount of debt destinationOut should account for in totalDebt
* idleDecrease Amount of baseAsset that was sent from the vault. > 0 only when tokenOut == baseAsset
* idleIncrease Amount of baseAsset that was claimed from Destination Vault
* @param params Rebalance out params
* @param destOutInfo The "out" destination vault info
* @return assetChange debt and idle change data
*/
function _handleRebalanceOut(
RebalanceOutParams memory params,
DestinationInfo storage destOutInfo
) private returns (IdleDebtUpdates memory assetChange) {
// Handle decrease (shares going "Out", cashing in shares and sending underlying back to swapper)
// If the tokenOut is _asset we assume they are taking idle
// which is already in the contract
if (params.amountOut > 0) {
if (params.tokenOut != address(params._baseAsset)) {
IDestinationVault dvOut = IDestinationVault(params.destinationOut);
// Snapshot our current shares so we know how much to back out
uint256 originalShareBal = dvOut.balanceOf(address(this));
// Burning our shares will claim any pending baseAsset
// rewards and send them to us.
// Get our starting balance
uint256 beforeBaseAssetBal = params._baseAsset.balanceOf(address(this));
// Withdraw underlying from the destination vault
// Shares are sent directly to the flashRebalance receiver
// slither-disable-next-line unused-return
dvOut.withdrawUnderlying(params.amountOut, params.receiver);
// Update the debt info snapshot
assetChange =
_recalculateDestInfo(destOutInfo, dvOut, originalShareBal, originalShareBal - params.amountOut);
// Capture any rewards we may have claimed as part of withdrawing
assetChange.totalIdleIncrease = params._baseAsset.balanceOf(address(this)) - beforeBaseAssetBal;
} else {
// If we are shutdown then the only operations we should be performing are those that get
// the base asset back to the vault. We shouldn't be sending out more
if (params._shutdown) {
revert VaultShutdown();
}
// Working with idle baseAsset which should be in the vault already
// Just send it out
IERC20(params.tokenOut).safeTransfer(params.receiver, params.amountOut);
assetChange.totalIdleDecrease = params.amountOut;
// We weren't dealing with any debt or pricing, just idle, so we can just mark
// it as safe
assetChange.pricesWereSafe = true;
}
}
}
function recalculateDestInfo(
DestinationInfo storage destInfo,
IDestinationVault destVault,
uint256 originalShares,
uint256 currentShares
) external returns (IdleDebtUpdates memory result) {
result = _recalculateDestInfo(destInfo, destVault, originalShares, currentShares);
}
/// @dev Will not revert on unsafe prices. Up to the caller.
function _recalculateDestInfo(
DestinationInfo storage destInfo,
IDestinationVault destVault,
uint256 originalShares,
uint256 currentShares
) private returns (IdleDebtUpdates memory result) {
// TODO: Trace the use of this fn and ensure that every is handling is pricesWereSafe
// Figure out what to back out of our totalDebt number.
// We could have had withdraws since the last snapshot which means our
// cached currentDebt number should be decreased based on the remaining shares
// totalDebt is decreased using the same proportion of shares method during withdrawals
// so this should represent whatever is remaining.
// Prices are per LP token and whether or not the prices are safe to use
// If they aren't safe then just continue and we'll get it on the next go around
(uint256 spotPrice, uint256 safePrice, bool isSpotSafe) = destVault.getRangePricesLP();
// Calculate what we're backing out based on the original shares
uint256 minPrice = spotPrice > safePrice ? safePrice : spotPrice;
uint256 maxPrice = spotPrice > safePrice ? spotPrice : safePrice;
// If we previously had shares, calculate how much of our cached numbers
// still remain as this will be deducted from the overall debt numbers
// TODO: Evaluate whether to round these up so we don't accumulate small amounts
// over time
uint256 prevOwnedShares = destInfo.ownedShares;
if (prevOwnedShares > 0) {
result.totalDebtDecrease = (destInfo.cachedDebtValue * originalShares) / prevOwnedShares;
result.totalMinDebtDecrease = (destInfo.cachedMinDebtValue * originalShares) / prevOwnedShares;
result.totalMaxDebtDecrease = (destInfo.cachedMaxDebtValue * originalShares) / prevOwnedShares;
}
// The overall debt value is the mid point of min and max
uint256 div = 10 ** destVault.decimals();
uint256 newDebtValue = (minPrice * currentShares + maxPrice * currentShares) / (div * 2);
result.pricesWereSafe = isSpotSafe;
result.totalDebtIncrease = newDebtValue;
result.totalMinDebtIncrease = minPrice * currentShares / div;
result.totalMaxDebtIncrease = maxPrice * currentShares / div;
// Save our current new values
destInfo.cachedDebtValue = newDebtValue;
destInfo.cachedMinDebtValue = result.totalMinDebtIncrease;
destInfo.cachedMaxDebtValue = result.totalMaxDebtIncrease;
destInfo.lastReport = block.timestamp;
destInfo.ownedShares = currentShares;
}
function totalAssetsTimeChecked(
StructuredLinkedList.List storage debtReportQueue,
mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo,
IAutopool.TotalAssetPurpose purpose
) external returns (uint256) {
IDestinationVault destVault = IDestinationVault(debtReportQueue.peekHead());
uint256 recalculatedTotalAssets = IAutopool(address(this)).totalAssets(purpose);
while (address(destVault) != address(0)) {
uint256 lastReport = destinationInfo[address(destVault)].lastReport;
if (lastReport + MAX_DEBT_REPORT_AGE_SECONDS > block.timestamp) {
// Its not stale
// This report is OK, we don't need to recalculate anything
break;
} else {
// It is stale, recalculate
//slither-disable-next-line unused-return
uint256 currentShares = destVault.balanceOf(address(this));
uint256 staleDebt;
uint256 extremePrice;
// Figure out exactly which price to use based on its purpose
if (purpose == IAutopool.TotalAssetPurpose.Deposit) {
// We use max value so that anything deposited is worth less
extremePrice = destVault.getUnderlyerCeilingPrice();
// Round down. We are subtracting this value out of the total so some left
// behind just increases the value which is what we want
staleDebt = destinationInfo[address(destVault)].cachedMaxDebtValue.mulDiv(
currentShares, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Down
);
} else if (purpose == IAutopool.TotalAssetPurpose.Withdraw) {
// We use min value so that we value the shares as worth less
extremePrice = destVault.getUnderlyerFloorPrice();
// Round up. We are subtracting this value out of the total so if we take a little
// extra it just decreases the value which is what we want
staleDebt = destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
currentShares, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
} else {
revert InvalidTotalAssetPurpose();
}
// Back out our stale debt, add in its new value
// Our goal is to find the most conservative value in each situation. If the current
// value we have represents that, then use it. Otherwise, use the new one.
uint256 newValue = (currentShares * extremePrice) / destVault.ONE();
if (purpose == IAutopool.TotalAssetPurpose.Deposit && staleDebt > newValue) {
newValue = staleDebt;
} else if (purpose == IAutopool.TotalAssetPurpose.Withdraw && staleDebt < newValue) {
newValue = staleDebt;
}
recalculatedTotalAssets = recalculatedTotalAssets + newValue - staleDebt;
}
destVault = IDestinationVault(debtReportQueue.getAdjacent(address(destVault), true));
}
return recalculatedTotalAssets;
}
function _updateDebtReporting(
StructuredLinkedList.List storage debtReportQueue,
mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo,
uint256 numToProcess
) external returns (IdleDebtUpdates memory result) {
numToProcess = Math.min(numToProcess, debtReportQueue.sizeOf());
for (uint256 i = 0; i < numToProcess; ++i) {
IDestinationVault destVault = IDestinationVault(debtReportQueue.popHead());
// Get the reward value we've earned. DV rewards are always in terms of base asset
// We track the gas used purely for off-chain stats purposes
// Main rewarder on DV's store the earned and liquidated rewards
// Extra rewarders are disabled at the DV level
uint256 claimGasUsed = gasleft();
uint256 beforeBaseAsset = IERC20(IAutopool(address(this)).asset()).balanceOf(address(this));
IMainRewarder(destVault.rewarder()).getReward(address(this), false);
uint256 claimedRewardValue =
IERC20(IAutopool(address(this)).asset()).balanceOf(address(this)) - beforeBaseAsset;
result.totalIdleIncrease += claimedRewardValue;
// Recalculate the debt info figuring out the change in
// total debt value we can roll up later
uint256 currentShareBalance = destVault.balanceOf(address(this));
AutopoolDebt.IdleDebtUpdates memory debtResult = _recalculateDestInfo(
destinationInfo[address(destVault)], destVault, currentShareBalance, currentShareBalance
);
result.totalDebtDecrease += debtResult.totalDebtDecrease;
result.totalDebtIncrease += debtResult.totalDebtIncrease;
result.totalMinDebtDecrease += debtResult.totalMinDebtDecrease;
result.totalMinDebtIncrease += debtResult.totalMinDebtIncrease;
result.totalMaxDebtDecrease += debtResult.totalMaxDebtDecrease;
result.totalMaxDebtIncrease += debtResult.totalMaxDebtIncrease;
// If we no longer have shares, then there's no reason to continue reporting on the destination.
// The strategy will only call for the info if its moving "out" of the destination
// and that will only happen if we have shares.
// A rebalance where we move "in" to the position will refresh the data at that time
if (currentShareBalance > 0) {
debtReportQueue.addToTail(address(destVault));
}
claimGasUsed -= gasleft();
emit DestinationDebtReporting(address(destVault), debtResult, claimedRewardValue, claimGasUsed);
}
}
function _initiateWithdrawInfo(
uint256 assets,
IAutopool.AssetBreakdown storage assetBreakdown
) private view returns (WithdrawInfo memory) {
uint256 idle = assetBreakdown.totalIdle;
WithdrawInfo memory info = WithdrawInfo({
currentIdle: idle,
// If idle can cover the full amount, then we want to pull all assets from there
// Otherwise, we want to pull from the market and only get idle if we exhaust the market
assetsFromIdle: assets > idle ? 0 : assets,
totalAssetsToPull: 0,
assetsToPull: 0,
assetsPulled: 0,
idleIncrease: 0,
debtDecrease: 0,
debtMinDecrease: 0,
debtMaxDecrease: 0,
totalMinDebt: assetBreakdown.totalDebtMin,
destinationRound: 0,
lastRoundSlippage: 0,
expectedAssets: 0
});
info.totalAssetsToPull = assets - info.assetsFromIdle;
// This var we use to track our progress later
info.assetsToPull = assets - info.assetsFromIdle;
// Idle + minDebt is the maximum amount of assets/debt we could burn during a withdraw.
// If the user is request more than that (like during a withdraw) we can just revert
// early without trying
if (info.totalAssetsToPull > info.currentIdle + info.totalMinDebt) {
revert TooFewAssets(assets, info.currentIdle + info.totalMinDebt);
}
return info;
}
function withdraw(
uint256 assets,
uint256 applicableTotalAssets,
IAutopool.AssetBreakdown storage assetBreakdown,
StructuredLinkedList.List storage withdrawalQueue,
mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo
) public returns (uint256 actualAssets, uint256 actualShares, uint256 debtBurned) {
WithdrawInfo memory info = _initiateWithdrawInfo(assets, assetBreakdown);
// Pull the market if there aren't enough funds in idle to cover the entire amount
// This flow is not bounded by a set number of shares. The user has requested X assets
// and a variable number of shares to burn so we don't have easy break out points like we do
// during redeem (like using debt burned). When we get slippage here and don't meet the requested assets
// we need to keep going if we can. This is tricky if we consider that (most of) our destinations are
// LP positions and we'll be swapping assets, so we can expect some slippage. Even
// if our minDebtValue numbers are up to date and perfectly accurate slippage could ensure we
// are always receiving less than we expect/calculate and we never hit the requested assets
// even though the owner would have shares to cover it. Under normal/expected conditions, our
// minDebtValue is lower than actual and we expect overall value to be going up, so we burn a tad
// more than we should and receive a tad more than we expect. This should cover us. However,
// in other conditions we have to be sure we aren't endlessly trying to approach 0 so we are tracking
// the slippage we received on the last pull, repricing, and applying an increasing multiplier until we either
// pull enough to cover or pull them all and/or move to the next destination.
uint256 dvSharesToBurn;
while (info.assetsToPull > 0) {
IDestinationVault destVault = IDestinationVault(withdrawalQueue.peekHead());
if (address(destVault) == address(0)) {
// TODO: This may be some NULL value too, check the underlying library
break;
}
uint256 dvShares = destVault.balanceOf(address(this));
{
uint256 dvSharesValue;
if (info.destinationRound == 0) {
// First time pulling
// We use the min debt value here because its a withdrawal and we're trying to cover an amount
// of assets. Undervaluing the shares may mean we pull more but given that we expect slippage
// that is desirable.
dvSharesValue = destinationInfo[address(destVault)].cachedMinDebtValue * dvShares
/ destinationInfo[address(destVault)].ownedShares;
} else {
// When we've pulled from this destination before, i.e. destinationRound > 0, then we
// know a more accurate exchange rate and its worse than we were expecting.
// We even will pad it a bit as we want to account for any additional slippage we
// may receive by say being farther down an AMM curve.
// dvSharesToBurn is the last value we used when pulling from this destination
// info.expectedAssets is how much we expected to get on that last pull
// info.expectedAssets - info.lastRoundSlippage is how much we actually received
uint256 paddedSlippage = info.lastRoundSlippage * (info.destinationRound + 10_000) / 10_000;
if (paddedSlippage < info.expectedAssets) {
dvSharesValue = (info.expectedAssets - paddedSlippage) * dvShares / dvSharesToBurn;
} else {
// This will just mean we pull all shares
dvSharesValue = 0;
}
}
if (dvSharesValue > info.assetsToPull) {
dvSharesToBurn = (dvShares * info.assetsToPull) / dvSharesValue;
// Only need to set it here because the only time we'll use it is if
// we don't exhaust all shares and have to try the destination again
info.expectedAssets = info.assetsToPull;
} else {
dvSharesToBurn = dvShares;
}
}
// Destination Vaults always burn the exact amount we instruct them to
uint256 pulledAssets = destVault.withdrawBaseAsset(dvSharesToBurn, address(this));
info.assetsPulled += pulledAssets;
// Calculate the totalDebt we'll need to remove based on the shares we're burning
// We're rounding up here so take care when actually applying to totalDebt
// The assets we calculated to pull are from the minDebt number we track so
// we'll use that one to ensure we properly account for slippage (the `pulled` var below)
// The other two debt numbers we just need to keep up to date.
uint256 debtMinDecrease = destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
info.debtMinDecrease += debtMinDecrease;
info.debtDecrease += destinationInfo[address(destVault)].cachedDebtValue.mulDiv(
dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
info.debtMaxDecrease += destinationInfo[address(destVault)].cachedMaxDebtValue.mulDiv(
dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
// If we've exhausted all shares we can remove the withdrawal from the queue
// We need to leave it in the debt report queue though so that our destination specific
// debt tracking values can be updated
if (dvShares == dvSharesToBurn) {
withdrawalQueue.popAddress(address(destVault));
info.destinationRound = 0;
info.lastRoundSlippage = 0;
} else {
// If we didn't burn all the shares and we received enough to cover our
// expected that means we'll break out below as we've hit our target
unchecked {
if (pulledAssets < info.expectedAssets) {
info.lastRoundSlippage = info.expectedAssets - pulledAssets;
if (info.destinationRound == 0) {
info.destinationRound = 100;
} else {
info.destinationRound *= 2;
}
}
}
}
// It's possible we'll get back more assets than we anticipate from a swap
// so if we do, throw it in idle and stop processing. You don't get more than we've calculated
if (info.assetsPulled >= info.totalAssetsToPull) {
info.idleIncrease += info.assetsPulled - info.totalAssetsToPull;
info.assetsPulled = info.totalAssetsToPull;
break;
}
info.assetsToPull -= pulledAssets;
}
// info.assetsToPull isn't safe to use past this point.
// It may or may not be accurate from the previous loop
// We didn't get enough assets from the debt pull
// See if we can get the rest from idle
if (info.assetsPulled < assets && info.currentIdle > 0) {
uint256 remaining = assets - info.assetsPulled;
if (remaining <= info.currentIdle) {
info.assetsFromIdle = remaining;
}
// We don't worry about the else case because if currentIdle can't
// cover remaining then we'll fail the `actualAssets < assets`
// check below and revert
}
debtBurned = info.assetsFromIdle + info.debtMinDecrease;
actualAssets = info.assetsFromIdle + info.assetsPulled;
if (actualAssets < assets) {
revert TooFewAssets(assets, actualAssets);
}
actualShares = IAutopool(address(this)).convertToShares(
Math.max(actualAssets, debtBurned),
applicableTotalAssets,
IAutopool(address(this)).totalSupply(),
Math.Rounding.Up
);
// Subtract what's taken out of idle from totalIdle
// We may also have some increase to account for it we over pulled
// or received better execution than we were anticipating
// slither-disable-next-line events-maths
assetBreakdown.totalIdle = info.currentIdle + info.idleIncrease - info.assetsFromIdle;
// Save off our various debt numbers
if (info.debtDecrease > assetBreakdown.totalDebt) {
assetBreakdown.totalDebt = 0;
} else {
assetBreakdown.totalDebt -= info.debtDecrease;
}
if (info.debtMinDecrease > info.totalMinDebt) {
assetBreakdown.totalDebtMin = 0;
} else {
assetBreakdown.totalDebtMin -= info.debtMinDecrease;
}
if (info.debtMaxDecrease > assetBreakdown.totalDebtMax) {
assetBreakdown.totalDebtMax = 0;
} else {
assetBreakdown.totalDebtMax -= info.debtMaxDecrease;
}
}
/// @notice Perform a removal of assets via the redeem path where the shares are the limiting factor.
/// This means we break out whenever we reach either `assets` retrieved or debt value equivalent to `assets` burned
function redeem(
uint256 assets,
uint256 applicableTotalAssets,
IAutopool.AssetBreakdown storage assetBreakdown,
StructuredLinkedList.List storage withdrawalQueue,
mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo
) public returns (uint256 actualAssets, uint256 actualShares, uint256 debtBurned) {
WithdrawInfo memory info = _initiateWithdrawInfo(assets, assetBreakdown);
// If not enough funds in idle, then pull what we need from destinations
bool exhaustedDestinations = false;
while (info.assetsToPull > 0) {
IDestinationVault destVault = IDestinationVault(withdrawalQueue.peekHead());
if (address(destVault) == address(0)) {
exhaustedDestinations = true;
break;
}
uint256 dvShares = destVault.balanceOf(address(this));
uint256 dvSharesToBurn = dvShares;
{
// Valuing these shares higher, rounding up, will result in us burning less of them
// in the event we don't burn all of them. Good thing.
uint256 dvSharesValue = destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
// If the dv shares we own are worth more than we need, limit the shares to burn
// Any extra we get will be dropped into idle
if (dvSharesValue > info.assetsToPull) {
uint256 limitedShares = (dvSharesToBurn * info.assetsToPull) / dvSharesValue;
// Final set for the actual shares we'll burn later
dvSharesToBurn = limitedShares;
}
}
// Destination Vaults always burn the exact amount we instruct them to
uint256 pulledAssets = destVault.withdrawBaseAsset(dvSharesToBurn, address(this));
info.assetsPulled += pulledAssets;
// Calculate the totalDebt we'll need to remove based on the shares we're burning
// We're rounding up here so take care when actually applying to totalDebt
// The assets we calculated to pull are from the minDebt number we track so
// we'll use that one to ensure we properly account for slippage (the `pulled` var below)
// The other two debt numbers we just need to keep up to date.
uint256 debtMinDecrease = destinationInfo[address(destVault)].cachedMinDebtValue.mulDiv(
dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
info.debtMinDecrease += debtMinDecrease;
info.debtDecrease += destinationInfo[address(destVault)].cachedDebtValue.mulDiv(
dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
info.debtMaxDecrease += destinationInfo[address(destVault)].cachedMaxDebtValue.mulDiv(
dvSharesToBurn, destinationInfo[address(destVault)].ownedShares, Math.Rounding.Up
);
// If we've exhausted all shares we can remove the withdrawal from the queue
// We need to leave it in the debt report queue though so that our destination specific
// debt tracking values can be updated
if (dvShares == dvSharesToBurn) {
withdrawalQueue.popAddress(address(destVault));
}
// It's possible we'll get back more assets than we anticipate from a swap
// so if we do, throw it in idle and stop processing. You don't get more than we've calculated
if (info.assetsPulled >= info.totalAssetsToPull) {
info.idleIncrease += info.assetsPulled - info.totalAssetsToPull;
info.assetsPulled = info.totalAssetsToPull;
break;
}
// Any deficiency in the amount we received is slippage. debtDecrease is what we expected
// to receive. If we received any extra, that's great we'll roll it forward so we burn
// less on the next loop.
uint256 pulled = Math.max(debtMinDecrease, pulledAssets);
if (pulled >= info.assetsToPull) {
// We either have enough assets, or we've burned the max debt we're allowed
break;
} else {
info.assetsToPull -= pulled;
}
// If we didn't exhaust all of the shares from the destination it means we
// assume we will get everything we need from there and everything else is slippage
if (dvShares != dvSharesToBurn) {
break;
}
}
// info.assetsToPull isn't safe to use past this point.
// It may or may not be accurate from the previous loop
// We didn't get enough assets from the debt pull
// See if we can get the rest from idle
// Check the debt burned though to ensure that we don't try to make up
// slippage incurred out of idle
if (
info.assetsPulled < assets && info.debtMinDecrease < assets && info.currentIdle > 0 && exhaustedDestinations
) {
uint256 remaining = assets - Math.max(info.assetsPulled, info.debtMinDecrease);
if (remaining < info.currentIdle) {
info.assetsFromIdle = remaining;
} else {
info.assetsFromIdle = info.currentIdle;
}
}
debtBurned = info.assetsFromIdle + info.debtMinDecrease;
actualAssets = info.assetsFromIdle + info.assetsPulled;
actualShares = IAutopool(address(this)).convertToShares(
debtBurned, applicableTotalAssets, IAutopool(address(this)).totalSupply(), Math.Rounding.Up
);
// Subtract what's taken out of idle from totalIdle
// We may also have some increase to account for it we over pulled
// or received better execution than we were anticipating
// slither-disable-next-line events-maths
assetBreakdown.totalIdle = info.currentIdle + info.idleIncrease - info.assetsFromIdle;
// Save off our various debt numbers
if (info.debtDecrease > assetBreakdown.totalDebt) {
assetBreakdown.totalDebt = 0;
} else {
assetBreakdown.totalDebt -= info.debtDecrease;
}
if (info.debtMinDecrease > info.totalMinDebt) {
assetBreakdown.totalDebtMin = 0;
} else {
assetBreakdown.totalDebtMin -= info.debtMinDecrease;
}
if (info.debtMaxDecrease > assetBreakdown.totalDebtMax) {
assetBreakdown.totalDebtMax = 0;
} else {
assetBreakdown.totalDebtMax -= info.debtMaxDecrease;
}
}
/**
* @notice Function to complete a withdrawal or redeem. This runs after shares to be burned and assets to be
* transferred are calculated.
* @param assets Amount of assets to be transferred to receiver.
* @param shares Amount of shares to be burned from owner.
* @param owner Owner of shares, user to burn shares from.
* @param receiver The receiver of the baseAsset.
* @param baseAsset Base asset of the Autopool.
* @param assetBreakdown Asset breakdown for the Autopool.
* @param tokenData Token data for the Autopool.
*/
function completeWithdrawal(
uint256 assets,
uint256 shares,
address owner,
address receiver,
IERC20 baseAsset,
IAutopool.AssetBreakdown storage assetBreakdown,
AutopoolToken.TokenData storage tokenData
) external {
if (msg.sender != owner) {
uint256 allowed = IAutopool(address(this)).allowance(owner, msg.sender);
if (allowed != type(uint256).max) {
if (shares > allowed) revert AmountExceedsAllowance(shares, allowed);
unchecked {
tokenData.approve(owner, msg.sender, allowed - shares);
}
}
}
tokenData.burn(owner, shares);
uint256 ts = IAutopool(address(this)).totalSupply();
emit Withdraw(msg.sender, receiver, owner, assets, shares);
emit Nav(assetBreakdown.totalIdle, assetBreakdown.totalDebt, ts);
baseAsset.safeTransfer(receiver, assets);
}
/**
* @notice A helper function to get estimates of what would happen on a withdraw or redeem.
* @dev Reverts all changing state.
* @param previewWithdraw Bool denoting whether to preview a redeem or withdrawal.
* @param assets Assets to be withdrawn or redeemed.
* @param applicableTotalAssets Operation dependent assets in the Autopool.
* @param functionCallEncoded Abi encoded function signature for recursive call.
* @param assetBreakdown Breakdown of vault assets from Autopool storage.
* @param withdrawalQueue Destination vault withdrawal queue from Autopool storage.
* @param destinationInfo Mapping of information for destinations.
* @return assetsAmount Preview of amount of assets to send to receiver.
* @return sharesAmount Preview of amount of assets to burn from owner.
*/
function preview(
bool previewWithdraw,
uint256 assets,
uint256 applicableTotalAssets,
bytes memory functionCallEncoded,
IAutopool.AssetBreakdown storage assetBreakdown,
StructuredLinkedList.List storage withdrawalQueue,
mapping(address => AutopoolDebt.DestinationInfo) storage destinationInfo
) external returns (uint256 assetsAmount, uint256 sharesAmount) {
if (msg.sender != address(this)) {
// Perform a recursive call the function in `funcCallEncoded`. This will result in a call back to
// the Autopool, and then this function. The intention is to reach the "else" block in this function.
// solhint-disable avoid-low-level-calls
// slither-disable-next-line missing-zero-check,low-level-calls
(bool success, bytes memory returnData) = address(this).call(functionCallEncoded);
// solhint-enable avoid-low-level-calls
// If the recursive call is successful, it means an unintended code path was taken.
if (success) {
revert Errors.UnreachableError();
}
bytes4 sharesAmountSig = bytes4(keccak256("SharesAndAssetsReceived(uint256,uint256)"));
// Extract the error signature (first 4 bytes) from the revert reason.
bytes4 errorSignature;
// solhint-disable no-inline-assembly
assembly {
errorSignature := mload(add(returnData, 0x20))
}
// If the error matches the expected signature, extract the amount from the revert reason and return.
if (errorSignature == sharesAmountSig) {
// Extract subsequent bytes for uint256.
assembly {
assetsAmount := mload(add(returnData, 0x24))
sharesAmount := mload(add(returnData, 0x44))
}
} else {
// If the error is not the expected one, forward the original revert reason.
assembly {
revert(add(32, returnData), mload(returnData))
}
}
// solhint-enable no-inline-assembly
}
// This branch is taken during the recursive call.
else {
// Perform the actual withdrawal or redeem logic to compute the amount. This will be reverted to
// simulate the action.
uint256 previewAssets;
uint256 previewShares;
if (previewWithdraw) {
(previewAssets, previewShares,) =
withdraw(assets, applicableTotalAssets, assetBreakdown, withdrawalQueue, destinationInfo);
} else {
(previewAssets, previewShares,) =
redeem(assets, applicableTotalAssets, assetBreakdown, withdrawalQueue, destinationInfo);
}
// Revert with the computed amount as an error.
revert SharesAndAssetsReceived(previewAssets, previewShares);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { IERC20Metadata } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
/// @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in https://eips.ethereum.org/EIPS/eip-4626
/// @dev Due to the nature of obtaining estimates for previewing withdraws and redeems, a few functions are not
/// view and therefore do not conform to eip 4626. These functions use state changing operations
/// to get accurate estimates, reverting after the preview amounts have been obtained.
interface IERC4626 is IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
);
/// @notice Returns the address of the underlying token used for the Vault for accounting, depositing, and
/// withdrawing.
/// @dev
/// - MUST be an ERC-20 token contract.
/// - MUST NOT revert.
function asset() external view returns (address assetTokenAddress);
/// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
/// @dev
/// - SHOULD include any compounding that occurs from yield.
/// - MUST be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT revert.
function totalAssets() external view returns (uint256 totalManagedAssets);
/// @notice Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an
/// ideal
/// scenario where all the conditions are met.
/// @dev
/// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT show any variations depending on the caller.
/// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
/// - MUST NOT revert.
///
/// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
/// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
/// from.
function convertToShares(uint256 assets) external view returns (uint256 shares);
/// @notice Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an
/// ideal
/// scenario where all the conditions are met.
/// @dev
/// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT show any variations depending on the caller.
/// - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
/// - MUST NOT revert.
///
/// NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
/// “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
/// from.
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/// @notice Returns the maximum amount of the underlying asset that can be deposited into the Vault for the
/// receiver,
/// through a deposit call.
/// @dev
/// - MUST return a limited value if receiver is subject to some deposit limit.
/// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
/// - MUST NOT revert.
function maxDeposit(address receiver) external returns (uint256 maxAssets);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block,
/// given
/// current on-chain conditions.
/// @dev
/// - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
/// call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
/// in the same transaction.
/// - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
/// deposit would be accepted, regardless if the user has enough tokens approved, etc.
/// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by depositing.
function previewDeposit(uint256 assets) external returns (uint256 shares);
/// @notice Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
/// @dev
/// - MUST emit the Deposit event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
/// deposit execution, and are accounted for during deposit.
/// - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
/// approving enough underlying tokens to the Vault contract, etc).
///
/// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/// @notice Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
/// @dev
/// - MUST return a limited value if receiver is subject to some mint limit.
/// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
/// - MUST NOT revert.
function maxMint(address receiver) external returns (uint256 maxShares);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
/// current on-chain conditions.
/// @dev
/// - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
/// in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
/// same transaction.
/// - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
/// would be accepted, regardless if the user has enough tokens approved, etc.
/// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by minting.
function previewMint(uint256 shares) external returns (uint256 assets);
/// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
/// @dev
/// - MUST emit the Deposit event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
/// execution, and are accounted for during mint.
/// - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
/// approving enough underlying tokens to the Vault contract, etc).
///
/// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/// @notice Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
/// Vault, through a withdraw call.
/// @dev
/// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
/// - MUST NOT revert.
function maxWithdraw(address owner) external returns (uint256 maxAssets);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
/// given current on-chain conditions.
/// @dev
/// - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
/// call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
/// called
/// in the same transaction.
/// - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
/// the withdrawal would be accepted, regardless if the user has enough shares, etc.
/// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by depositing.
function previewWithdraw(uint256 assets) external returns (uint256 shares);
/// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver.
/// @dev
/// - MUST emit the Withdraw event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
/// withdraw execution, and are accounted for during withdraw.
/// - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
/// not having enough shares, etc).
///
/// Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
/// Those methods should be performed separately.
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
/// through a redeem call.
/// @dev
/// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
/// - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
/// - MUST NOT revert.
function maxRedeem(address owner) external returns (uint256 maxShares);
/// @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
/// given current on-chain conditions.
/// @dev
/// - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
/// in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
/// same transaction.
/// - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
/// redemption would be accepted, regardless if the user has enough shares, etc.
/// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
/// - MUST NOT revert.
///
/// NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
/// share price or some other type of condition, meaning the depositor will lose assets by redeeming.
function previewRedeem(uint256 shares) external returns (uint256 assets);
/// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver.
/// @dev
/// - MUST emit the Withdraw event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
/// redeem execution, and are accounted for during redeem.
/// - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
/// not having enough shares, etc).
///
/// NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
/// Those methods should be performed separately.
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { IStrategy } from "src/interfaces/strategy/IStrategy.sol";
interface IAutopoolStrategy {
enum RebalanceDirection {
In,
Out
}
/// @notice verify that a rebalance (swap between destinations) meets all the strategy constraints
/// @dev Signature identical to IStrategy.verifyRebalance
function verifyRebalance(
IStrategy.RebalanceParams memory,
IStrategy.SummaryStats memory
) external returns (bool, string memory message);
/// @notice called by the Autopool when NAV is updated
/// @dev can only be called by the strategy's registered Autopool
/// @param navPerShare The navPerShare to record
function navUpdate(uint256 navPerShare) external;
/// @notice called by the Autopool when a rebalance is completed
/// @dev can only be called by the strategy's registered Autopool
/// @param rebalanceParams The parameters for the rebalance that was executed
function rebalanceSuccessfullyExecuted(IStrategy.RebalanceParams memory rebalanceParams) external;
/// @notice called by the Autopool during rebalance process
/// @param rebalanceParams The parameters for the rebalance that was executed
function getRebalanceOutSummaryStats(IStrategy.RebalanceParams memory rebalanceParams)
external
returns (IStrategy.SummaryStats memory outSummary);
/// @notice the number of days to pause rebalancing due to NAV decay
function pauseRebalancePeriodInDays() external view returns (uint16);
/// @notice the number of seconds gap between consecutive rebalances
function rebalanceTimeGapInSeconds() external view returns (uint256);
/// @notice destinations trading a premium above maxPremium will be blocked from new capital deployments
function maxPremium() external view returns (int256); // 100% = 1e18
/// @notice destinations trading a discount above maxDiscount will be blocked from new capital deployments
function maxDiscount() external view returns (int256); // 100% = 1e18
/// @notice the allowed staleness of stats data before a revert occurs
function staleDataToleranceInSeconds() external view returns (uint40);
/// @notice the swap cost offset period to initialize the strategy with
function swapCostOffsetInitInDays() external view returns (uint16);
/// @notice the number of violations required to trigger a tightening of the swap cost offset period (1 to 10)
function swapCostOffsetTightenThresholdInViolations() external view returns (uint16);
/// @notice the number of days to decrease the swap offset period for each tightening step
function swapCostOffsetTightenStepInDays() external view returns (uint16);
/// @notice the number of days since a rebalance required to trigger a relaxing of the swap cost offset period
function swapCostOffsetRelaxThresholdInDays() external view returns (uint16);
/// @notice the number of days to increase the swap offset period for each relaxing step
function swapCostOffsetRelaxStepInDays() external view returns (uint16);
// slither-disable-start similar-names
/// @notice the maximum the swap cost offset period can reach. This is the loosest the strategy will be
function swapCostOffsetMaxInDays() external view returns (uint16);
/// @notice the minimum the swap cost offset period can reach. This is the most conservative the strategy will be
function swapCostOffsetMinInDays() external view returns (uint16);
/// @notice the number of days for the first NAV decay comparison (e.g., 30 days)
function navLookback1InDays() external view returns (uint8);
/// @notice the number of days for the second NAV decay comparison (e.g., 60 days)
function navLookback2InDays() external view returns (uint8);
/// @notice the number of days for the third NAV decay comparison (e.g., 90 days)
function navLookback3InDays() external view returns (uint8);
// slither-disable-end similar-names
/// @notice the maximum slippage that is allowed for a normal rebalance
function maxNormalOperationSlippage() external view returns (uint256); // 100% = 1e18
/// @notice the maximum amount of slippage to allow when a destination is trimmed due to constraint violations
/// recommend setting this higher than maxNormalOperationSlippage
function maxTrimOperationSlippage() external view returns (uint256); // 100% = 1e18
/// @notice the maximum amount of slippage to allow when a destinationVault has been shutdown
/// shutdown for a vault is abnormal and means there is an issue at that destination
/// recommend setting this higher than maxNormalOperationSlippage
function maxEmergencyOperationSlippage() external view returns (uint256); // 100% = 1e18
/// @notice the maximum amount of slippage to allow when the Autopool has been shutdown
function maxShutdownOperationSlippage() external view returns (uint256); // 100% = 1e18
/// @notice the maximum discount used for price return
function maxAllowedDiscount() external view returns (int256); // 18 precision
/// @notice model weight used for LSTs base yield, 1e6 is the highest
function weightBase() external view returns (uint256);
/// @notice model weight used for DEX fee yield, 1e6 is the highest
function weightFee() external view returns (uint256);
/// @notice model weight used for incentive yield
function weightIncentive() external view returns (uint256);
/// @notice model weight used slashing costs
function weightSlashing() external view returns (uint256);
/// @notice model weight applied to an LST discount when exiting the position
function weightPriceDiscountExit() external view returns (int256);
/// @notice model weight applied to an LST discount when entering the position
function weightPriceDiscountEnter() external view returns (int256);
/// @notice model weight applied to an LST premium when entering or exiting the position
function weightPricePremium() external view returns (int256);
/// @notice initial value of the swap cost offset to use
function swapCostOffsetInit() external view returns (uint16);
/// @notice initial lst price gap tolerance
function defaultLstPriceGapTolerance() external view returns (uint256);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { IBaseRewarder } from "src/interfaces/rewarders/IBaseRewarder.sol";
import { IExtraRewarder } from "src/interfaces/rewarders/IExtraRewarder.sol";
interface IMainRewarder is IBaseRewarder {
error ExtraRewardsNotAllowed();
event ExtraRewardAdded(address reward);
event ExtraRewardsCleared();
event ExtraRewardRemoved(address reward);
/**
* @notice Adds an ExtraRewarder contract address to the extraRewards array.
* @param reward The address of the ExtraRewarder contract.
*/
function addExtraReward(address reward) external;
/**
* @notice Removes a list of ExtraRewarder contract addresses from the extraRewards array.
*/
function removeExtraRewards(address[] calldata _rewards) external;
/**
* @notice Withdraws the specified amount of tokens from the vault for the specified account, and transfers all
* rewards for the account from this contract and any linked extra reward contracts.
* @param account The address of the account to withdraw tokens and claim rewards for.
* @param amount The amount of tokens to withdraw.
* @param claim If true, claims all rewards for the account from this contract and any linked extra reward
* contracts.
*/
function withdraw(address account, uint256 amount, bool claim) external;
/**
* @notice Clears the extraRewards array.
*/
function clearExtraRewards() external;
/**
* @notice Claims and transfers all rewards for the specified account from this contract and any linked extra reward
* contracts.
* @dev If claimExtras is true, also claims all rewards from linked extra reward contracts.
* @param account The address of the account to claim rewards for.
* @param claimExtras If true, claims rewards from linked extra reward contracts.
*/
function getReward(address account, bool claimExtras) external;
/**
* @notice Number of extra rewards currently registered
*/
function extraRewardsLength() external view returns (uint256);
/**
* @notice Get the extra rewards array values
*/
function extraRewards() external view returns (address[] memory);
/**
* @notice Get the rewarder at the specified index
*/
function getExtraRewarder(uint256 index) external view returns (IExtraRewarder);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { Address } from "openzeppelin-contracts/utils/Address.sol";
library Errors {
using Address for address;
///////////////////////////////////////////////////////////////////
// Set errors
///////////////////////////////////////////////////////////////////
error AccessDenied();
error ZeroAddress(string paramName);
error ZeroAmount();
error InsufficientBalance(address token);
error AssetNotAllowed(address token);
error NotImplemented();
error InvalidAddress(address addr);
error InvalidParam(string paramName);
error InvalidParams();
error UnsafePrice(address token, uint256 spotPrice, uint256 safePrice);
error AlreadySet(string param);
error AlreadyRegistered(address param);
error SlippageExceeded(uint256 expected, uint256 actual);
error ArrayLengthMismatch(uint256 length1, uint256 length2, string details);
error ItemNotFound();
error ItemExists();
error MissingRole(bytes32 role, address user);
error RegistryItemMissing(string item);
error NotRegistered();
// Used to check storage slot is empty before setting.
error MustBeZero();
// Used to check storage slot set before deleting.
error MustBeSet();
error ApprovalFailed(address token);
error FlashLoanFailed(address token, uint256 amount);
error SystemMismatch(address source1, address source2);
error InvalidToken(address token);
error UnreachableError();
error InvalidSigner(address signer);
error InvalidChainId(uint256 chainId);
error SenderMismatch(address recipient, address sender);
function verifyNotZero(address addr, string memory paramName) internal pure {
if (addr == address(0)) {
revert ZeroAddress(paramName);
}
}
function verifyNotZero(bytes32 key, string memory paramName) internal pure {
if (key == bytes32(0)) {
revert InvalidParam(paramName);
}
}
function verifyNotEmpty(string memory val, string memory paramName) internal pure {
if (bytes(val).length == 0) {
revert InvalidParam(paramName);
}
}
function verifyNotZero(uint256 num, string memory paramName) internal pure {
if (num == 0) {
revert InvalidParam(paramName);
}
}
function verifySystemsMatch(address component1, address component2) internal view {
bytes memory call = abi.encodeWithSignature("getSystemRegistry()");
address registry1 = abi.decode(component1.functionStaticCall(call), (address));
address registry2 = abi.decode(component2.functionStaticCall(call), (address));
if (registry1 != registry2) {
revert SystemMismatch(component1, component2);
}
}
function verifyArrayLengths(uint256 length1, uint256 length2, string memory details) internal pure {
if (length1 != length2) {
revert ArrayLengthMismatch(length1, length2, details);
}
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
library LibAdapter {
using SafeERC20 for IERC20;
address public constant CURVE_REGISTRY_ETH_ADDRESS_POINTER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
error MinLpAmountNotReached();
error LpTokenAmountMismatch();
error NoNonZeroAmountProvided();
error InvalidBalanceChange();
// Utils
function _approve(IERC20 token, address spender, uint256 amount) internal {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance > 0) {
token.safeDecreaseAllowance(spender, currentAllowance);
}
token.safeIncreaseAllowance(spender, amount);
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { IERC20Metadata as IERC20 } from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IBaseAssetVault } from "src/interfaces/vault/IBaseAssetVault.sol";
import { IMainRewarder } from "src/interfaces/rewarders/IMainRewarder.sol";
import { IDexLSTStats } from "src/interfaces/stats/IDexLSTStats.sol";
interface IDestinationVault is IBaseAssetVault, IERC20 {
enum VaultShutdownStatus {
Active,
Deprecated,
Exploit
}
error LogicDefect();
error BaseAmountReceived(uint256 amount);
/* ******************************** */
/* View */
/* ******************************** */
/// @notice A full unit of this vault
// solhint-disable-next-line func-name-mixedcase
function ONE() external view returns (uint256);
/// @notice The asset that is deposited into the vault
function underlying() external view returns (address);
/// @notice The asset that rewards and withdrawals to the Autopool are denominated in
/// @inheritdoc IBaseAssetVault
function baseAsset() external view override returns (address);
/// @notice Debt balance of underlying asset that is in contract. This
/// value includes only assets that are known as debt by the rest of the
/// system (i.e. transferred in on rebalance), and does not include
/// extraneous amounts of underlyer that may have ended up in this contract.
function internalDebtBalance() external view returns (uint256);
/// @notice Debt balance of underlyering asset staked externally. This value only
/// includes assets known as debt to the rest of the system, and does not include
/// any assets staked on behalf of the DV in external contracts.
function externalDebtBalance() external view returns (uint256);
/// @notice Returns true value of _underlyer in DV. Debt + tokens that may have
/// been transferred into the contract outside of rebalance.
function internalQueriedBalance() external view returns (uint256);
/// @notice Returns true value of staked _underlyer in external contract. This
/// will include any _underlyer that has been staked on behalf of the DV.
function externalQueriedBalance() external view returns (uint256);
/// @notice Balance of underlying debt, sum of `externalDebtBalance()` and `internalDebtBalance()`.
function balanceOfUnderlyingDebt() external view returns (uint256);
/// @notice Rewarder for this vault
function rewarder() external view returns (address);
/// @notice Exchange this destination vault points to
function exchangeName() external view returns (string memory);
/// @notice The type of pool associated with this vault
function poolType() external view returns (string memory);
/// @notice If the pool only deals in ETH when adding or removing liquidity
function poolDealInEth() external view returns (bool);
/// @notice Tokens that base asset can be swapped into
function underlyingTokens() external view returns (address[] memory);
/* ******************************** */
/* Events */
/* ******************************** */
event Donated(address sender, uint256 amount);
event Withdraw(
uint256 target, uint256 actual, uint256 debtLoss, uint256 claimLoss, uint256 fromIdle, uint256 fromDebt
);
event UpdateSignedMessage(bytes32 hash, bool flag);
/* ******************************** */
/* Errors */
/* ******************************** */
error ZeroAddress(string paramName);
error InvalidShutdownStatus(VaultShutdownStatus status);
/* ******************************** */
/* Functions */
/* ******************************** */
/// @notice Setup the contract. These will be cloned so no constructor
/// @param baseAsset_ Base asset of the system. WETH/USDC/etc
/// @param underlyer_ Underlying asset the vault will wrap
/// @param rewarder_ Reward tracker for this vault
/// @param incentiveCalculator_ Incentive calculator for this vault
/// @param additionalTrackedTokens_ Additional tokens that should be considered 'tracked'
/// @param params_ Any extra parameters needed to setup the contract
function initialize(
IERC20 baseAsset_,
IERC20 underlyer_,
IMainRewarder rewarder_,
address incentiveCalculator_,
address[] memory additionalTrackedTokens_,
bytes memory params_
) external;
function getRangePricesLP() external returns (uint256 spotPrice, uint256 safePrice, bool isSpotSafe);
/// @notice Calculates the current value of a portion of the debt based on shares
/// @dev Queries the current value of all tokens we have deployed, whether its a single place, multiple, staked, etc
/// @param shares The number of shares to value
/// @return value The current value of our debt in terms of the baseAsset
function debtValue(uint256 shares) external returns (uint256 value);
/// @notice Collects any earned rewards from staking, incentives, etc. Transfers to sender
/// @dev Should be limited to LIQUIDATOR_MANAGER. Rewards must be collected before claimed
/// @return amounts amount of rewards claimed for each token
/// @return tokens tokens claimed
function collectRewards() external returns (uint256[] memory amounts, address[] memory tokens);
/// @notice Pull any non-tracked token to the specified destination
/// @dev Should be limited to TOKEN_RECOVERY_MANAGER
function recover(address[] calldata tokens, uint256[] calldata amounts, address[] calldata destinations) external;
/// @notice Recovers any extra underlying both in DV and staked externally not tracked as debt.
/// @dev Should be limited to TOKEN_SAVER_ROLE.
/// @param destination The address to send excess underlyer to.
function recoverUnderlying(address destination) external;
/// @notice Deposit underlying to receive destination vault shares
/// @param amount amount of base lp asset to deposit
function depositUnderlying(uint256 amount) external returns (uint256 shares);
/// @notice Withdraw underlying by burning destination vault shares
/// @param shares amount of destination vault shares to burn
/// @param to destination of the underlying asset
/// @return amount underlyer amount 'to' received
function withdrawUnderlying(uint256 shares, address to) external returns (uint256 amount);
/// @notice Burn specified shares for underlyer swapped to base asset
/// @param shares amount of vault shares to burn
/// @param to destination of the base asset
/// @return amount base asset amount 'to' received
function withdrawBaseAsset(uint256 shares, address to) external returns (uint256 amount);
/// @notice Mark this vault as shutdown so that autoPools can react
function shutdown(VaultShutdownStatus reason) external;
/// @notice True if the vault has been shutdown
function isShutdown() external view returns (bool);
/// @notice Returns the reason for shutdown (or `Active` if not shutdown)
function shutdownStatus() external view returns (VaultShutdownStatus);
/// @notice Stats contract for this vault
function getStats() external view returns (IDexLSTStats);
/// @notice get the marketplace rewards
/// @return rewardTokens list of reward token addresses
/// @return rewardRates list of reward rates
function getMarketplaceRewards() external returns (uint256[] memory rewardTokens, uint256[] memory rewardRates);
/// @notice Get the address of the underlying pool the vault points to
/// @return poolAddress address of the underlying pool
function getPool() external view returns (address poolAddress);
/// @notice Gets the spot price of the underlying LP token
/// @dev Price validated to be inside our tolerance against safe price. Will revert if outside.
/// @return price Value of 1 unit of the underlying LP token in terms of the base asset
function getValidatedSpotPrice() external returns (uint256 price);
/// @notice Gets the safe price of the underlying LP token
/// @dev Price validated to be inside our tolerance against spot price. Will revert if outside.
/// @return price Value of 1 unit of the underlying LP token in terms of the base asset
function getValidatedSafePrice() external returns (uint256 price);
/// @notice Get the lowest price we can get for the LP token
/// @dev This price can be attacked is not validate to be in any range
/// @return price Value of 1 unit of the underlying LP token in terms of the base asset
function getUnderlyerFloorPrice() external returns (uint256 price);
/// @notice Get the highest price we can get for the LP token
/// @dev This price can be attacked is not validate to be in any range
/// @return price Value of 1 unit of the underlying LP token in terms of the base asset
function getUnderlyerCeilingPrice() external returns (uint256 price);
/// @notice Set or unset a hash as a signed message
/// @dev Should be limited to DESTINATION_VAULTS_UPDATER. The set hash is used to vaildate a signature.
/// This signature can be potentially used to claim offchain rewards earned by Destination Vaults.
/// @param hash bytes32 hash of a payload
/// @param flag boolean flag to indicate a validity of hash
function setMessage(bytes32 hash, bool flag) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { IERC3156FlashBorrower } from "openzeppelin-contracts/interfaces/IERC3156FlashBorrower.sol";
interface IStrategy {
/* ******************************** */
/* Events */
/* ******************************** */
event DestinationVaultAdded(address destination);
event DestinationVaultRemoved(address destination);
event WithdrawalQueueSet(address[] destinations);
event AddedToRemovalQueue(address destination);
event RemovedFromRemovalQueue(address destination);
error InvalidDestinationVault();
error RebalanceFailed(string message);
/// @notice gets the list of supported destination vaults for the Autopool/Strategy
/// @return _destinations List of supported destination vaults
function getDestinations() external view returns (address[] memory _destinations);
/// @notice add supported destination vaults for the Autopool/Strategy
/// @param _destinations The list of destination vaults to add
function addDestinations(address[] calldata _destinations) external;
/// @notice remove supported destination vaults for the Autopool/Strategy
/// @param _destinations The list of destination vaults to remove
function removeDestinations(address[] calldata _destinations) external;
/// @param destinationIn The address / lp token of the destination vault that will increase
/// @param tokenIn The address of the underlyer token that will be provided by the swapper
/// @param amountIn The amount of the underlying LP tokens that will be received
/// @param destinationOut The address of the destination vault that will decrease
/// @param tokenOut The address of the underlyer token that will be received by the swapper
/// @param amountOut The amount of the tokenOut that will be received by the swapper
struct RebalanceParams {
address destinationIn;
address tokenIn;
uint256 amountIn;
address destinationOut;
address tokenOut;
uint256 amountOut;
}
/// @param destination The address / lp token of the destination vault
/// @param baseApr Base Apr is the yield generated by staking rewards
/// @param feeApr Yield for pool trading fees
/// @param incentiveApr Incentives for LP
/// @param safeTotalSupply Safe supply for LP tokens
/// @param priceReturn Return from price movement to & away from peg
/// @param maxDiscount Max discount to peg
/// @param maxPremium Max premium to peg
/// @param ownedShares Shares owned for this destination
/// @param compositeReturn Total return combined from the individual yield components
/// @param pricePerShare Price per share
/// @param slashingCost The loss due to slashing of the backing
struct SummaryStats {
address destination;
uint256 baseApr;
uint256 feeApr;
uint256 incentiveApr;
uint256 safeTotalSupply;
int256 priceReturn;
int256 maxDiscount;
int256 maxPremium;
uint256 ownedShares;
int256 compositeReturn;
uint256 pricePerShare;
uint256 slashingCost;
}
/// @notice rebalance the Autopool from the tokenOut (decrease) to the tokenIn (increase)
/// This uses a flash loan to receive the tokenOut to reduce the working capital requirements of the swapper
/// @param receiver The contract receiving the tokens, needs to implement the
/// `onFlashLoan(address user, address token, uint256 amount, uint256 fee, bytes calldata)` interface
/// @param params Parameters by which to perform the rebalance
/// @param data A data parameter to be passed on to the `receiver` for any custom use
function flashRebalance(
IERC3156FlashBorrower receiver,
RebalanceParams calldata params,
bytes calldata data
) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC3156FlashBorrower.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC3156 FlashBorrower, as defined in
* https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
*
* _Available since v4.1._
*/
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "IERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}// SPDX-License-Identifier: MIT
pragma solidity =0.8.17;
/**
* @title StructuredLinkedList
* @author Vittorio Minacori (https://github.com/vittominacori)
* @dev An utility library for using sorted linked list data structures in your Solidity project.
* @notice Adapted from
* https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/src/contracts/libraries/StructuredLinkedList.sol
*/
library StructuredLinkedList {
uint256 private constant _NULL = 0;
uint256 private constant _HEAD = 0;
bool private constant _PREV = false;
bool private constant _NEXT = true;
struct List {
uint256 size;
mapping(uint256 => mapping(bool => uint256)) list;
}
/**
* @dev Checks if the list exists
* @param self stored linked list from contract
* @return bool true if list exists, false otherwise
*/
function listExists(List storage self) public view returns (bool) {
// if the head nodes previous or next pointers both point to itself, then there are no items in the list
if (self.list[_HEAD][_PREV] != _HEAD || self.list[_HEAD][_NEXT] != _HEAD) {
return true;
} else {
return false;
}
}
/**
* @dev Checks if the node exists
* @param self stored linked list from contract
* @param _node a node to search for
* @return bool true if node exists, false otherwise
*/
function nodeExists(List storage self, uint256 _node) public view returns (bool) {
if (self.list[_node][_PREV] == _HEAD && self.list[_node][_NEXT] == _HEAD) {
if (self.list[_HEAD][_NEXT] == _node) {
return true;
} else {
return false;
}
} else {
return true;
}
}
/**
* @dev Returns the number of elements in the list
* @param self stored linked list from contract
* @return uint256
*/
// slither-disable-next-line dead-code
function sizeOf(List storage self) public view returns (uint256) {
return self.size;
}
/**
* @dev Gets the head of the list
* @param self stored linked list from contract
* @return uint256 the head of the list
*/
function getHead(List storage self) public view returns (uint256) {
return self.list[_HEAD][_NEXT];
}
/**
* @dev Gets the head of the list
* @param self stored linked list from contract
* @return uint256 the head of the list
*/
function getTail(List storage self) public view returns (uint256) {
return self.list[_HEAD][_PREV];
}
/**
* @dev Returns the links of a node as a tuple
* @param self stored linked list from contract
* @param _node id of the node to get
* @return bool, uint256, uint256 true if node exists or false otherwise, previous node, next node
*/
// slither-disable-next-line dead-code
function getNode(List storage self, uint256 _node) public view returns (bool, uint256, uint256) {
if (!nodeExists(self, _node)) {
return (false, 0, 0);
} else {
return (true, self.list[_node][_PREV], self.list[_node][_NEXT]);
}
}
/**
* @dev Returns the link of a node `_node` in direction `_direction`.
* @param self stored linked list from contract
* @param _node id of the node to step from
* @param _direction direction to step in
* @return bool, uint256 true if node exists or false otherwise, node in _direction
*/
// slither-disable-next-line dead-code
function getAdjacent(List storage self, uint256 _node, bool _direction) public view returns (bool, uint256) {
if (!nodeExists(self, _node)) {
return (false, 0);
} else {
uint256 adjacent = self.list[_node][_direction];
return (adjacent != _HEAD, adjacent);
}
}
/**
* @dev Returns the link of a node `_node` in direction `_NEXT`.
* @param self stored linked list from contract
* @param _node id of the node to step from
* @return bool, uint256 true if node exists or false otherwise, next node
*/
// slither-disable-next-line dead-code
function getNextNode(List storage self, uint256 _node) public view returns (bool, uint256) {
return getAdjacent(self, _node, _NEXT);
}
/**
* @dev Returns the link of a node `_node` in direction `_PREV`.
* @param self stored linked list from contract
* @param _node id of the node to step from
* @return bool, uint256 true if node exists or false otherwise, previous node
*/
// slither-disable-next-line dead-code
function getPreviousNode(List storage self, uint256 _node) public view returns (bool, uint256) {
return getAdjacent(self, _node, _PREV);
}
/**
* @dev Insert node `_new` beside existing node `_node` in direction `_NEXT`.
* @param self stored linked list from contract
* @param _node existing node
* @param _new new node to insert
* @return bool true if success, false otherwise
*/
// slither-disable-next-line dead-code
function insertAfter(List storage self, uint256 _node, uint256 _new) public returns (bool) {
return _insert(self, _node, _new, _NEXT);
}
/**
* @dev Insert node `_new` beside existing node `_node` in direction `_PREV`.
* @param self stored linked list from contract
* @param _node existing node
* @param _new new node to insert
* @return bool true if success, false otherwise
*/
// slither-disable-next-line dead-code
function insertBefore(List storage self, uint256 _node, uint256 _new) public returns (bool) {
return _insert(self, _node, _new, _PREV);
}
/**
* @dev Removes an entry from the linked list
* @param self stored linked list from contract
* @param _node node to remove from the list
* @return uint256 the removed node
*/
function remove(List storage self, uint256 _node) public returns (uint256) {
if ((_node == _NULL) || (!nodeExists(self, _node))) {
return 0;
}
_createLink(self, self.list[_node][_PREV], self.list[_node][_NEXT], _NEXT);
delete self.list[_node][_PREV];
delete self.list[_node][_NEXT];
self.size -= 1;
return _node;
}
/**
* @dev Pushes an entry to the head of the linked list
* @param self stored linked list from contract
* @param _node new entry to push to the head
* @return bool true if success, false otherwise
*/
function pushFront(List storage self, uint256 _node) public returns (bool) {
return _push(self, _node, _NEXT);
}
/**
* @dev Pushes an entry to the tail of the linked list
* @param self stored linked list from contract
* @param _node new entry to push to the tail
* @return bool true if success, false otherwise
*/
function pushBack(List storage self, uint256 _node) public returns (bool) {
return _push(self, _node, _PREV);
}
/**
* @dev Pops the first entry from the head of the linked list
* @param self stored linked list from contract
* @return uint256 the removed node
*/
// slither-disable-next-line dead-code
function popFront(List storage self) public returns (uint256) {
return _pop(self, _NEXT);
}
/**
* @dev Pops the first entry from the tail of the linked list
* @param self stored linked list from contract
* @return uint256 the removed node
*/
// slither-disable-next-line dead-code
function popBack(List storage self) public returns (uint256) {
return _pop(self, _PREV);
}
/**
* @dev Pushes an entry to the head of the linked list
* @param self stored linked list from contract
* @param _node new entry to push to the head
* @param _direction push to the head (_NEXT) or tail (_PREV)
* @return bool true if success, false otherwise
*/
function _push(List storage self, uint256 _node, bool _direction) private returns (bool) {
return _insert(self, _HEAD, _node, _direction);
}
/**
* @dev Pops the first entry from the linked list
* @param self stored linked list from contract
* @param _direction pop from the head (_NEXT) or the tail (_PREV)
* @return uint256 the removed node
*/
// slither-disable-next-line dead-code
function _pop(List storage self, bool _direction) private returns (uint256) {
uint256 adj;
(, adj) = getAdjacent(self, _HEAD, _direction);
return remove(self, adj);
}
/**
* @dev Insert node `_new` beside existing node `_node` in direction `_direction`.
* @param self stored linked list from contract
* @param _node existing node
* @param _new new node to insert
* @param _direction direction to insert node in
* @return bool true if success, false otherwise
*/
function _insert(List storage self, uint256 _node, uint256 _new, bool _direction) private returns (bool) {
if (!nodeExists(self, _new) && nodeExists(self, _node)) {
uint256 c = self.list[_node][_direction];
_createLink(self, _node, _new, _direction);
_createLink(self, _new, c, _direction);
self.size += 1;
return true;
}
return false;
}
/**
* @dev Creates a bidirectional link between two nodes on direction `_direction`
* @param self stored linked list from contract
* @param _node existing node
* @param _link node to link to in the _direction
* @param _direction direction to insert node in
*/
function _createLink(List storage self, uint256 _node, uint256 _link, bool _direction) private {
self.list[_link][!_direction] = _node;
self.list[_node][_direction] = _link;
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17; // their version was using 8.12?
import { StructuredLinkedList } from "src/strategy/StructuredLinkedList.sol";
// https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/src/contracts/libraries/StructuredLinkedList.sol
library WithdrawalQueue {
using StructuredLinkedList for StructuredLinkedList.List;
error CannotInsertZeroAddress();
error UnexpectedNodeRemoved();
error AddToHeadFailed();
error AddToTailFailed();
error NodeDoesNotExist();
/// @notice Returns true if the address is in the queue.
function addressExists(StructuredLinkedList.List storage queue, address addr) public view returns (bool) {
return StructuredLinkedList.nodeExists(queue, _addressToUint(addr));
}
/// @notice Returns the current head.
function peekHead(StructuredLinkedList.List storage queue) public view returns (address) {
return _uintToAddress(StructuredLinkedList.getHead(queue));
}
/// @notice Returns the current tail.
function peekTail(StructuredLinkedList.List storage queue) public view returns (address) {
return _uintToAddress(StructuredLinkedList.getTail(queue));
}
/// @notice Returns the number of items in the queue
function sizeOf(StructuredLinkedList.List storage queue) public view returns (uint256) {
return StructuredLinkedList.sizeOf(queue);
}
/// @notice Return all items in the queue
/// @dev Enumerates from head to tail
function getList(StructuredLinkedList.List storage self) public view returns (address[] memory list) {
uint256 size = self.sizeOf();
list = new address[](size);
if (size > 0) {
uint256 lastNode = self.getHead();
list[0] = _uintToAddress(lastNode);
for (uint256 i = 1; i < size; ++i) {
(bool exists, uint256 node) = self.getAdjacent(lastNode, true);
if (!exists) {
revert NodeDoesNotExist();
}
list[i] = _uintToAddress(node);
lastNode = node;
}
}
}
/// @notice Returns the current tail.
function popHead(StructuredLinkedList.List storage queue) public returns (address) {
return _uintToAddress(StructuredLinkedList.popFront(queue));
}
/// @notice remove address toRemove from queue if it exists.
function popAddress(StructuredLinkedList.List storage queue, address toRemove) public {
uint256 addrAsUint = _addressToUint(toRemove);
uint256 _removedNode = StructuredLinkedList.remove(queue, addrAsUint);
if (!((_removedNode == addrAsUint) || (_removedNode == 0))) {
revert UnexpectedNodeRemoved();
}
}
/// @notice returns true if there are no addresses in queue.
function isEmpty(StructuredLinkedList.List storage queue) public view returns (bool) {
return !StructuredLinkedList.listExists(queue);
}
/// @notice if addr in queue, move it to the top
// if addr not in queue, add it to the top of the queue.
// if queue is empty, make a new queue with addr as the only node
function addToHead(StructuredLinkedList.List storage queue, address addr) public {
if (addr == address(0)) {
revert CannotInsertZeroAddress();
}
popAddress(queue, addr);
bool success = StructuredLinkedList.pushFront(queue, _addressToUint(addr));
if (!success) {
revert AddToHeadFailed();
}
}
function getAdjacent(
StructuredLinkedList.List storage queue,
address addr,
bool direction
) public view returns (address) {
(bool exists, uint256 addrNum) = queue.getAdjacent(_addressToUint(addr), direction);
if (!exists) {
return address(0);
}
return _uintToAddress(addrNum);
}
/// @notice if addr in queue, move it to the end
// if addr not in queue, add it to the end of the queue.
// if queue is empty, make a new queue with addr as the only node
function addToTail(StructuredLinkedList.List storage queue, address addr) public {
if (addr == address(0)) {
revert CannotInsertZeroAddress();
}
popAddress(queue, addr);
bool success = StructuredLinkedList.pushBack(queue, _addressToUint(addr));
if (!success) {
revert AddToTailFailed();
}
}
function _addressToUint(address addr) private pure returns (uint256) {
return uint256(uint160(addr));
}
function _uintToAddress(uint256 x) private pure returns (address) {
return address(uint160(x));
}
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
interface IBaseRewarder {
event RewardAdded(
uint256 reward,
uint256 rewardRate,
uint256 lastUpdateBlock,
uint256 periodInBlockFinish,
uint256 historicalRewards
);
event UserRewardUpdated(
address indexed user, uint256 amount, uint256 rewardPerTokenStored, uint256 lastUpdateBlock
);
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
event QueuedRewardsUpdated(uint256 startingQueuedRewards, uint256 startingNewRewards, uint256 queuedRewards);
event AddedToWhitelist(address indexed wallet);
event RemovedFromWhitelist(address indexed wallet);
event TokeLockDurationUpdated(uint256 newDuration);
/**
* @notice Claims and transfers all rewards for the specified account
*/
function getReward() external;
/**
* @notice Stakes the specified amount of tokens for the specified account.
* @param account The address of the account to stake tokens for.
* @param amount The amount of tokens to stake.
*/
function stake(address account, uint256 amount) external;
/**
* @notice Calculate the earned rewards for an account.
* @param account Address of the account.
* @return The earned rewards for the given account.
*/
function earned(address account) external view returns (uint256);
/**
* @notice Calculates the rewards per token for the current block.
* @dev The total amount of rewards available in the system is fixed, and it needs to be distributed among the users
* based on their token balances and staking duration.
* Rewards per token represent the amount of rewards that each token is entitled to receive at the current block.
* The calculation takes into account the reward rate, the time duration since the last update,
* and the total supply of tokens in the staking pool.
* @return The updated rewards per token value for the current block.
*/
function rewardPerToken() external view returns (uint256);
/**
* @notice Get the current reward rate per block.
* @return The current reward rate per block.
*/
function rewardRate() external view returns (uint256);
/**
* @notice Get the current TOKE lock duration.
* @return The current TOKE lock duration.
*/
function tokeLockDuration() external view returns (uint256);
/**
* @notice Get the last block where rewards are applicable.
* @return The last block number where rewards are applicable.
*/
function lastBlockRewardApplicable() external view returns (uint256);
/**
* @notice The total amount of tokens staked
*/
function totalSupply() external view returns (uint256);
/**
* @notice The amount of tokens staked for the specified account
* @param account The address of the account to get the balance of
*/
function balanceOf(address account) external view returns (uint256);
/**
* @notice Queue new rewards to be distributed.
* @param newRewards The amount of new rewards to be queued.
*/
function queueNewRewards(uint256 newRewards) external;
/**
* @notice Token distributed as rewards
* @return reward token address
*/
function rewardToken() external view returns (address);
/**
* @notice Add an address to the whitelist.
* @param wallet The address to be added to the whitelist.
*/
function addToWhitelist(address wallet) external;
/**
* @notice Remove an address from the whitelist.
* @param wallet The address to be removed from the whitelist.
*/
function removeFromWhitelist(address wallet) external;
/**
* @notice Check if an address is whitelisted.
* @param wallet The address to be checked.
* @return bool indicating if the address is whitelisted.
*/
function isWhitelisted(address wallet) external view returns (bool);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { IBaseRewarder } from "src/interfaces/rewarders/IBaseRewarder.sol";
interface IExtraRewarder is IBaseRewarder {
/**
* @notice Withdraws the specified amount of tokens from the vault for the specified account.
* @param account The address of the account to withdraw tokens for.
* @param amount The amount of tokens to withdraw.
*/
function withdraw(address account, uint256 amount) external;
/**
* @notice Claims and transfers all rewards for the specified account from this contract.
* @param account The address of the account to claim rewards for.
*/
function getReward(address account) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
interface IBaseAssetVault {
/// @notice Asset that this Vault primarily manages
/// @dev Vault decimals should be the same as the baseAsset
function baseAsset() external view returns (address);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
import { ILSTStats } from "src/interfaces/stats/ILSTStats.sol";
/// @title Return stats DEXs with LSTs
interface IDexLSTStats {
event DexSnapshotTaken(uint256 snapshotTimestamp, uint256 priorFeeApr, uint256 newFeeApr, uint256 unfilteredFeeApr);
struct StakingIncentiveStats {
// time-weighted average total supply to prevent spikes/attacks from impacting rebalancing
uint256 safeTotalSupply;
// rewardTokens, annualizedRewardAmounts, and periodFinishForRewards will match indexes
// they are split to workaround an issue with forge having nested structs
// address of the reward tokens
address[] rewardTokens;
// the annualized reward rate for the reward token
uint256[] annualizedRewardAmounts;
// the timestamp for when the rewards are set to terminate
uint40[] periodFinishForRewards;
// incentive rewards score. max 48, min 0
uint8 incentiveCredits;
}
struct DexLSTStatsData {
uint256 lastSnapshotTimestamp;
uint256 feeApr;
uint256[] reservesInEth;
StakingIncentiveStats stakingIncentiveStats;
ILSTStats.LSTStatsData[] lstStatsData;
}
/// @notice Get the current stats for the DEX with underlying LST tokens
/// @dev Returned data is a combination of current data and filtered snapshots
/// @return dexLSTStatsData current data on the DEX
function current() external returns (DexLSTStatsData memory dexLSTStatsData);
}// SPDX-License-Identifier: UNLICENSED
// Copyright (c) 2023 Tokemak Foundation. All rights reserved.
pragma solidity 0.8.17;
/// @title Return stats on base LSTs
interface ILSTStats {
struct LSTStatsData {
uint256 lastSnapshotTimestamp;
uint256 baseApr;
int256 discount; // positive number is a discount, negative is a premium
uint24[10] discountHistory; // 7 decimal precision
uint40[5] discountTimestampByPercent; // each index is the timestamp that the token reached that discount
uint256[] slashingCosts;
uint256[] slashingTimestamps;
}
/// @notice Used to transfer LST snapshot data to other chain.
struct LSTDestinationInfo {
uint256 snapshotTimestamp;
uint256 newBaseApr;
uint256 currentEthPerToken;
}
/// @notice Get the current stats for the LST
/// @dev Returned data is a combination of current data and filtered snapshots
/// @return lstStatsData current data on the LST
function current() external returns (LSTStatsData memory lstStatsData);
/// @notice Get the EthPerToken (or Share) for the LST
/// @return ethPerShare the backing eth for the LST
function calculateEthPerToken() external view returns (uint256 ethPerShare);
/// @notice Get if the underlying LST token is rebasing
/// @return rebasing is true if the lst is a rebasing token
function isRebasing() external view returns (bool rebasing);
}{
"remappings": [
"forge-std/=lib/forge-std/src/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"src/=src/",
"test/=test/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
"erc4626-tests/=lib/erc4626-tests/",
"prb-math/=lib/prb-math/",
"crytic/properties/=lib/properties/",
"ERC4626/=lib/properties/lib/ERC4626/contracts/",
"properties/=lib/properties/contracts/",
"solmate/=lib/properties/lib/solmate/src/",
"usingtellor/=lib/usingtellor/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "london",
"viaIR": false,
"libraries": {
"src/destinations/adapters/BalancerBeethovenAdapter.sol": {
"BalancerBeethovenAdapter": "0xeE9d4bDA0a124157D05756e48e12C67289833191"
},
"src/destinations/adapters/CurveV2FactoryCryptoAdapter.sol": {
"CurveV2FactoryCryptoAdapter": "0xe46E7634d6e0BC954407078818fF62eDfF414184"
},
"src/destinations/adapters/rewards/AuraRewardsAdapter.sol": {
"AuraRewards": "0x27401aca0EA294195187F6aE0673451AB01e5471"
},
"src/destinations/adapters/rewards/ConvexRewardsAdapter.sol": {
"ConvexRewards": "0x08F295fD04B52Ab6a7f8A86bC9A0A1c0EA8aDE01"
},
"src/destinations/adapters/rewards/MaverickRewardsAdapter.sol": {
"MaverickRewardsAdapter": "0x487F4bdf1201Ac0eD5D8c9A9b706A3b62De5249e"
},
"src/destinations/adapters/rewards/MaverickStakingAdapter.sol": {
"MaverickStakingAdapter": "0x123d88094FEfB727Bef3906123485C2AF49810bC"
},
"src/destinations/adapters/staking/AuraAdapter.sol": {
"AuraStaking": "0x9f121874565cC071Fd2E830DF2021987210DD853"
},
"src/destinations/adapters/staking/ConvexAdapter.sol": {
"ConvexStaking": "0x6c2f91978004B0A769c622dfe352EdF331044d49"
},
"src/libs/BalancerUtilities.sol": {
"BalancerUtilities": "0xa986Cc1bc9C8987E7b679F4edF8e065079BA461B"
},
"src/strategy/StructuredLinkedList.sol": {
"StructuredLinkedList": "0x6C566a67b34CFed9821FC7433750A1391fa97989"
},
"src/strategy/WithdrawalQueue.sol": {
"WithdrawalQueue": "0xC754773B0e8CaFbD17e978be3c31ef34869ba733"
},
"src/strategy/libs/Incentives.sol": {
"Incentives": "0x451cE891E340A22bB6c2CE046827D4d69f571648"
},
"src/strategy/libs/PriceReturn.sol": {
"PriceReturn": "0xF8e01E7120C0C4cF8Ba90d12DDdF0E3bE9e3DBaD"
},
"src/strategy/libs/SummaryStats.sol": {
"SummaryStats": "0x88eA6A9B3d27Ee782f8c3e6bEE6392fabAD59796"
},
"src/vault/libs/Autopool4626.sol": {
"Autopool4626": "0x1Dc315a9d153Dc516A007358C173df51C78C61e4"
},
"src/vault/libs/AutopoolDebt.sol": {
"AutopoolDebt": "0xc2d3450E1b80136aE8fa3bC6EE8E4b702C7D4942"
},
"src/vault/libs/AutopoolDestinations.sol": {
"AutopoolDestinations": "0x93B5d679C4FC5b5705cba9D8e505Ff66b962C7aD"
},
"src/vault/libs/AutopoolFees.sol": {
"AutopoolFees": "0x35400b052d96ce4d9943AEeE9f36beB63eCB6b2b"
},
"src/vault/libs/AutopoolToken.sol": {
"AutopoolToken": "0xb916038B047C637bD2be297A7480708Ba74E1A82"
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"name":"AlreadySet","type":"error"},{"inputs":[{"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"InvalidFee","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"fees","type":"uint256"},{"indexed":false,"internalType":"address","name":"feeSink","type":"address"},{"indexed":false,"internalType":"uint256","name":"mintedShares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAssets","type":"uint256"}],"name":"FeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newFeeSink","type":"address"}],"name":"FeeSinkSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"lastPeriodicFeeTake","type":"uint256"}],"name":"LastPeriodicFeeTakeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"navPerShare","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"NewNavShareFeeMark","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint48","name":"timeSeconds","type":"uint48"}],"name":"NewProfitUnlockTime","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"NewTotalAssetsHighWatermark","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"fees","type":"uint256"},{"indexed":false,"internalType":"address","name":"feeSink","type":"address"},{"indexed":false,"internalType":"uint256","name":"mintedShares","type":"uint256"}],"name":"PeriodicFeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"PeriodicFeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newPeriodicFeeSink","type":"address"}],"name":"PeriodicFeeSinkSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"RebalanceFeeHighWaterMarkEnabledSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"StreamingFeeSet","type":"event"},{"inputs":[],"name":"FEE_DIVISOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_BPS_PROFIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PERIODIC_FEE_BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_IN_YEAR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]Contract Creation Code
61185a61003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100ff5760003560e01c8063a6b8b9b9116100a1578063dbe5b32a11610070578063dbe5b32a1461023f578063e6f63c311461025f578063f2d0157f14610268578063fe5c822d1461027357600080fd5b8063a6b8b9b9146101bf578063a8dad0e4146101df578063b4f86aa3146101ff578063c0b1cd551461021f57600080fd5b8063567f0302116100dd578063567f03021461016b5780635dcc93911461018b5780636ac5b383146101965780639e93ad8e146101b657600080fd5b8063093295221461010457806325ae9ed11461012657806325f371531461014b575b600080fd5b81801561011057600080fd5b5061012461011f36600461146c565b610293565b005b61013961013436600461146c565b610408565b60405190815260200160405180910390f35b81801561015757600080fd5b5061012461016636600461148e565b610485565b81801561017757600080fd5b506101246101863660046114e7565b6105a3565b6101396301e1338081565b8180156101a257600080fd5b506101246101b1366004611513565b61061b565b61013961271081565b8180156101cb57600080fd5b506101246101da36600461152c565b610662565b8180156101eb57600080fd5b506101396101fa366004611568565b6106c3565b81801561020b57600080fd5b5061012461021a36600461146c565b6109a8565b81801561022b57600080fd5b5061013961023a3660046115b1565b610a0d565b81801561024b57600080fd5b5061012461025a36600461152c565b610c4e565b6101396103e881565b610139633b9aca0081565b81801561027f57600080fd5b5061012461028e36600461146c565b610ca7565b61271081106102bd5760405163179c637760e11b8152600481018290526024015b60405180910390fd5b80826006018190555060003090506000816001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561030b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061032f91906115fd565b905080156103cf576000826001600160a01b03166301e1d1146040518163ffffffff1660e01b8152600401602060405180830381865afa158015610377573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061039b91906115fd565b905080156103c457816103b06127108361162c565b6103ba9190611659565b60078601556103cd565b61271060078601555b505b6040518381527fa5a1883d6ed0251432b77439e74f9f28d7e8de68179fd83ad64088d0fbf5c4e69060200160405180910390a150505050565b8154600090600160301b900465ffffffffffff1642811115610465578354633b9aca009061044590600160601b900465ffffffffffff164261166d565b8560010154610454919061162c565b61045e9190611659565b915061047e565b801561047e573060009081526020849052604090205491505b5092915050565b825465ffffffffffff191665ffffffffffff821690811784556000036105635730600090815260208390526040902054801561054757835465ffffffffffff60601b1916600160601b4265ffffffffffff16021784556040516322fb941560e11b815273b916038b047c637bd2be297a7480708ba74e1a82906345f7282a9061051690869030908690600401611680565b60006040518083038186803b15801561052e57600080fd5b505af4158015610542573d6000803e3d6000fd5b505050505b5082546bffffffffffff00000000000019168355600060018401555b60405165ffffffffffff821681527f7bcaed1fa1a86488b0377927c7ff08ecd0fb989f4dfd1b423508aa6e2fe937ac9060200160405180910390a1505050565b600982015481151560ff9091161515036105d05760405163a741a04560e01b815260040160405180910390fd5b60098201805460ff19168215159081179091556040519081527f382ebb619d17502f814820a053c45e37e44f30a9ac712365abcef37514a650f0906020015b60405180910390a15050565b42600382018190556127106007830155600882018190556040518181527f47958863cd1b5cac74ca9982b61a60e67c3bc853ca3738349b17f83c2405289b9060200161060f565b6040516001600160a01b03821681527f8734efd4cf8ef698241e9b7e91438b4d9ec78cc03dfd9bd7c69ff334116358599060200160405180910390a160049190910180546001600160a01b0319166001600160a01b03909216919091179055565b6000846000036106d55750600061099f565b83600101546000036106e957600184018690555b811561082057600484015460058501546001600160a01b0390911690801580159061071c57506001600160a01b03821615155b156107e2576000866003015442610733919061166d565b905060006107546107466127108561162c565b83906301e133806001610d6c565b9050600061076485838c8e610dcb565b9050610770818b61169f565b60405163c67e515160e01b8152909a5073b916038b047c637bd2be297a7480708ba74e1a829063c67e5151906107ae908b9089908690600401611680565b60006040518083038186803b1580156107c657600080fd5b505af41580156107da573d6000803e3d6000fd5b505050505050505b42600387018190556040519081527f47958863cd1b5cac74ca9982b61a60e67c3bc853ca3738349b17f83c2405289b9060200160405180910390a150505b60008561082f6127108961162c565b6108399190611659565b905060006108498642848b610e8b565b9050808211156108e057600087610860838561166d565b61086a919061162c565b9050600061088f886006015460026127106108859190611796565b8491906001610d6c565b905080156108dd576108c187828a60060154858d8f8e60000160009054906101000a90046001600160a01b031661111a565b9850886108d06127108c61162c565b6108da9190611659565b93505b50505b80821015806108f45750600986015460ff16155b15610943576007860182905542600887018190556040805184815260208101929092527f54c6541604fd73f621d678966c0c0a2715f9a29d8aa09c74e0062fcf2aab47cc910160405180910390a15b878660010154101561099957600186018890554260028701819055604080518a815260208101929092527fc9de6c198a49cffb8cbf85ef1767a1c6e342be97f0b87397734248c82fb2dab1910160405180910390a15b86925050505b95945050505050565b6103e88111156109ce5760405163179c637760e11b8152600481018290526024016102b4565b6040518181527f27496b61db7c8f1c73a471fe8908a718000c413e4401dcf0ec205b30ea02d3cb9060200160405180910390a161ffff16600590910155565b865460009065ffffffffffff16801580610a25575084155b15610a335783915050610c43565b600080858188610a438c8461166d565b610a4d908c61162c565b610a579190611659565b905080821115610aa2578615610aa2576000610a73828461166d565b9050808810610a9057925082610a89818461166d565b9250610aa0565b879350610a9d848461166d565b92505b505b81811115610ac357610ab4828261166d565b9350610ac0848361169f565b91505b600084610ad0858a61166d565b610ada919061169f565b905087811115610b73576000610af0898361166d565b90508d73b916038b047c637bd2be297a7480708ba74e1a8263c67e5151909130846040518463ffffffff1660e01b8152600401610b2f93929190611680565b60006040518083038186803b158015610b4757600080fd5b505af4158015610b5b573d6000803e3d6000fd5b50505050808a610b6b919061169f565b995050610c06565b87811015610c06576000610b87828a61166d565b90508d73b916038b047c637bd2be297a7480708ba74e1a826345f7282a909130846040518463ffffffff1660e01b8152600401610bc693929190611680565b60006040518083038186803b158015610bde57600080fd5b505af4158015610bf2573d6000803e3d6000fd5b50505050808a610c02919061166d565b9950505b80600003610c1657600060018f01555b600081118015610c265750600086115b15610c3957610c398e87868b8986611266565b8896505050505050505b979650505050505050565b6040516001600160a01b03821681527f93f45e69cdabde5c8296469e04ee6765b806aa9c3ef27883e43d1c3d227c4f229060200160405180910390a181546001600160a01b0319166001600160a01b0391909116179055565b6000610cb38383610408565b905080600003610cc257505050565b825442600160301b90910465ffffffffffff161115610cfc57825465ffffffffffff60601b1916600160601b4265ffffffffffff16021783555b6040516322fb941560e11b815273b916038b047c637bd2be297a7480708ba74e1a82906345f7282a90610d3790859030908690600401611680565b60006040518083038186803b158015610d4f57600080fd5b505af4158015610d63573d6000803e3d6000fd5b50505050505050565b600080610d7a86868661133e565b90506001836002811115610d9057610d906117a5565b148015610dad575060008480610da857610da8611643565b868809115b15610dc057610dbd60018261169f565b90505b90505b949350505050565b6000610dd88483856113f3565b90506000610dfd61271080610ded868961162c565b610df79190611659565b90611432565b6040805160008152602081018590529192506001600160a01b0388169130917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a37f57541f28ce268adbe5fa488e716321b8a31a74aa5461c983ad6293f322ebb3ea818784604051610e7a93929190611680565b60405180910390a150949350505050565b6007840154600090808203610ea4576000915050610dc3565b600986015460ff16610eb7579050610dc3565b60006018603c80896008015489610ece919061166d565b610ed89190611659565b610ee29190611659565b610eec9190611659565b9050610258811115610f02578492505050610dc3565b603c81118015610f1457506102588111155b15611110576000306001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7d91906117bb565b90506000610f8c82600a611796565b60018a01549091506000818810610fa35781610fa5565b875b90506000828911610fb65782610fb8565b885b90506000610fc6858361162c565b85610fd3856103e761162c565b610fdd919061162c565b610fe79190611659565b90506000610ff66001886117de565b61100190600a611796565b8361100c888761162c565b6110169190611659565b611020908861166d565b61102b90606361162c565b6110359190611659565b90506000611043828461169f565b90506000611052603c8b61166d565b905060005b611062601983611659565b8110156110c6576103e87d90e40fbeea1d3a4abc8955e946fe31cdcf66f634e1000000000000000000611096601986611796565b6110a09190611659565b6110aa908e61162c565b6110b49190611659565b9b506110bf816117f7565b9050611057565b5060005b6110d5601983611810565b811015611105576103e86110e9848e61162c565b6110f39190611659565b9b506110fe816117f7565b90506110ca565b505050505050505050505b5095945050505050565b60006001600160a01b038216611131575082610c43565b600061113e8785876113f3565b60405163c67e515160e01b815290915073b916038b047c637bd2be297a7480708ba74e1a829063c67e51519061117c908c9087908690600401611680565b60006040518083038186803b15801561119457600080fd5b505af41580156111a8573d6000803e3d6000fd5b5050505080856111b8919061169f565b6040805160008152602081018490529196506001600160a01b0385169130917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3604080518981526001600160a01b038516602082015290810182905260608101879052608081018590527f3552e4b795fabf338ac18811c7db8508569032709558e19c88df5499681d7b139060a00160405180910390a15092979650505050505050565b8554600090600160301b900465ffffffffffff16428111156112a35761128c428261166d565b611296878761166d565b6112a0919061162c565b91505b6000836112b0898761162c565b6112ba908561169f565b6112c49190611659565b9050806112d5633b9aca008661162c565b6112df9190611659565b60018a01556112ee814261169f565b895471ffffffffffffffffffffffff0000000000001916600160301b65ffffffffffff9283160265ffffffffffff60601b191617600160601b429290921691909102179098555050505050505050565b60008080600019858709858702925082811083820303915050806000036113785783828161136e5761136e611643565b04925050506113ec565b80841161138457600080fd5b600084868809851960019081018716968790049682860381900495909211909303600082900391909104909201919091029190911760038402600290811880860282030280860282030280860282030280860282030280860282030280860290910302029150505b9392505050565b600080612710611403858761162c565b61140d9190611659565b905061099f8184816114216127108961162c565b61142b919061166d565b6001610d6c565b60008215611460578161144660018561166d565b6114509190611659565b61145b90600161169f565b611463565b60005b90505b92915050565b6000806040838503121561147f57600080fd5b50508035926020909101359150565b6000806000606084860312156114a357600080fd5b8335925060208401359150604084013565ffffffffffff811681146114c757600080fd5b809150509250925092565b803580151581146114e257600080fd5b919050565b600080604083850312156114fa57600080fd5b8235915061150a602084016114d2565b90509250929050565b60006020828403121561152557600080fd5b5035919050565b6000806040838503121561153f57600080fd5b8235915060208301356001600160a01b038116811461155d57600080fd5b809150509250929050565b600080600080600060a0868803121561158057600080fd5b853594506020860135935060408601359250606086013591506115a5608087016114d2565b90509295509295909350565b600080600080600080600060e0888a0312156115cc57600080fd5b505085359760208701359750604087013596606081013596506080810135955060a0810135945060c0013592509050565b60006020828403121561160f57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761146657611466611616565b634e487b7160e01b600052601260045260246000fd5b60008261166857611668611643565b500490565b8181038181111561146657611466611616565b9283526001600160a01b03919091166020830152604082015260600190565b8082018082111561146657611466611616565b600181815b808511156116ed5781600019048211156116d3576116d3611616565b808516156116e057918102915b93841c93908002906116b7565b509250929050565b60008261170457506001611466565b8161171157506000611466565b816001811461172757600281146117315761174d565b6001915050611466565b60ff84111561174257611742611616565b50506001821b611466565b5060208310610133831016604e8410600b8410161715611770575081810a611466565b61177a83836116b2565b806000190482111561178e5761178e611616565b029392505050565b600061146360ff8416836116f5565b634e487b7160e01b600052602160045260246000fd5b6000602082840312156117cd57600080fd5b815160ff811681146113ec57600080fd5b60ff828116828216039081111561146657611466611616565b60006001820161180957611809611616565b5060010190565b60008261181f5761181f611643565b50069056fea2646970667358221220e9a2087556b9a15c5aa603c30b196108a135a028fd6b22078e16bae992699c7964736f6c63430008110033
Deployed Bytecode
0x7335400b052d96ce4d9943aeee9f36beb63ecb6b2b30146080604052600436106100ff5760003560e01c8063a6b8b9b9116100a1578063dbe5b32a11610070578063dbe5b32a1461023f578063e6f63c311461025f578063f2d0157f14610268578063fe5c822d1461027357600080fd5b8063a6b8b9b9146101bf578063a8dad0e4146101df578063b4f86aa3146101ff578063c0b1cd551461021f57600080fd5b8063567f0302116100dd578063567f03021461016b5780635dcc93911461018b5780636ac5b383146101965780639e93ad8e146101b657600080fd5b8063093295221461010457806325ae9ed11461012657806325f371531461014b575b600080fd5b81801561011057600080fd5b5061012461011f36600461146c565b610293565b005b61013961013436600461146c565b610408565b60405190815260200160405180910390f35b81801561015757600080fd5b5061012461016636600461148e565b610485565b81801561017757600080fd5b506101246101863660046114e7565b6105a3565b6101396301e1338081565b8180156101a257600080fd5b506101246101b1366004611513565b61061b565b61013961271081565b8180156101cb57600080fd5b506101246101da36600461152c565b610662565b8180156101eb57600080fd5b506101396101fa366004611568565b6106c3565b81801561020b57600080fd5b5061012461021a36600461146c565b6109a8565b81801561022b57600080fd5b5061013961023a3660046115b1565b610a0d565b81801561024b57600080fd5b5061012461025a36600461152c565b610c4e565b6101396103e881565b610139633b9aca0081565b81801561027f57600080fd5b5061012461028e36600461146c565b610ca7565b61271081106102bd5760405163179c637760e11b8152600481018290526024015b60405180910390fd5b80826006018190555060003090506000816001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561030b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061032f91906115fd565b905080156103cf576000826001600160a01b03166301e1d1146040518163ffffffff1660e01b8152600401602060405180830381865afa158015610377573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061039b91906115fd565b905080156103c457816103b06127108361162c565b6103ba9190611659565b60078601556103cd565b61271060078601555b505b6040518381527fa5a1883d6ed0251432b77439e74f9f28d7e8de68179fd83ad64088d0fbf5c4e69060200160405180910390a150505050565b8154600090600160301b900465ffffffffffff1642811115610465578354633b9aca009061044590600160601b900465ffffffffffff164261166d565b8560010154610454919061162c565b61045e9190611659565b915061047e565b801561047e573060009081526020849052604090205491505b5092915050565b825465ffffffffffff191665ffffffffffff821690811784556000036105635730600090815260208390526040902054801561054757835465ffffffffffff60601b1916600160601b4265ffffffffffff16021784556040516322fb941560e11b815273b916038b047c637bd2be297a7480708ba74e1a82906345f7282a9061051690869030908690600401611680565b60006040518083038186803b15801561052e57600080fd5b505af4158015610542573d6000803e3d6000fd5b505050505b5082546bffffffffffff00000000000019168355600060018401555b60405165ffffffffffff821681527f7bcaed1fa1a86488b0377927c7ff08ecd0fb989f4dfd1b423508aa6e2fe937ac9060200160405180910390a1505050565b600982015481151560ff9091161515036105d05760405163a741a04560e01b815260040160405180910390fd5b60098201805460ff19168215159081179091556040519081527f382ebb619d17502f814820a053c45e37e44f30a9ac712365abcef37514a650f0906020015b60405180910390a15050565b42600382018190556127106007830155600882018190556040518181527f47958863cd1b5cac74ca9982b61a60e67c3bc853ca3738349b17f83c2405289b9060200161060f565b6040516001600160a01b03821681527f8734efd4cf8ef698241e9b7e91438b4d9ec78cc03dfd9bd7c69ff334116358599060200160405180910390a160049190910180546001600160a01b0319166001600160a01b03909216919091179055565b6000846000036106d55750600061099f565b83600101546000036106e957600184018690555b811561082057600484015460058501546001600160a01b0390911690801580159061071c57506001600160a01b03821615155b156107e2576000866003015442610733919061166d565b905060006107546107466127108561162c565b83906301e133806001610d6c565b9050600061076485838c8e610dcb565b9050610770818b61169f565b60405163c67e515160e01b8152909a5073b916038b047c637bd2be297a7480708ba74e1a829063c67e5151906107ae908b9089908690600401611680565b60006040518083038186803b1580156107c657600080fd5b505af41580156107da573d6000803e3d6000fd5b505050505050505b42600387018190556040519081527f47958863cd1b5cac74ca9982b61a60e67c3bc853ca3738349b17f83c2405289b9060200160405180910390a150505b60008561082f6127108961162c565b6108399190611659565b905060006108498642848b610e8b565b9050808211156108e057600087610860838561166d565b61086a919061162c565b9050600061088f886006015460026127106108859190611796565b8491906001610d6c565b905080156108dd576108c187828a60060154858d8f8e60000160009054906101000a90046001600160a01b031661111a565b9850886108d06127108c61162c565b6108da9190611659565b93505b50505b80821015806108f45750600986015460ff16155b15610943576007860182905542600887018190556040805184815260208101929092527f54c6541604fd73f621d678966c0c0a2715f9a29d8aa09c74e0062fcf2aab47cc910160405180910390a15b878660010154101561099957600186018890554260028701819055604080518a815260208101929092527fc9de6c198a49cffb8cbf85ef1767a1c6e342be97f0b87397734248c82fb2dab1910160405180910390a15b86925050505b95945050505050565b6103e88111156109ce5760405163179c637760e11b8152600481018290526024016102b4565b6040518181527f27496b61db7c8f1c73a471fe8908a718000c413e4401dcf0ec205b30ea02d3cb9060200160405180910390a161ffff16600590910155565b865460009065ffffffffffff16801580610a25575084155b15610a335783915050610c43565b600080858188610a438c8461166d565b610a4d908c61162c565b610a579190611659565b905080821115610aa2578615610aa2576000610a73828461166d565b9050808810610a9057925082610a89818461166d565b9250610aa0565b879350610a9d848461166d565b92505b505b81811115610ac357610ab4828261166d565b9350610ac0848361169f565b91505b600084610ad0858a61166d565b610ada919061169f565b905087811115610b73576000610af0898361166d565b90508d73b916038b047c637bd2be297a7480708ba74e1a8263c67e5151909130846040518463ffffffff1660e01b8152600401610b2f93929190611680565b60006040518083038186803b158015610b4757600080fd5b505af4158015610b5b573d6000803e3d6000fd5b50505050808a610b6b919061169f565b995050610c06565b87811015610c06576000610b87828a61166d565b90508d73b916038b047c637bd2be297a7480708ba74e1a826345f7282a909130846040518463ffffffff1660e01b8152600401610bc693929190611680565b60006040518083038186803b158015610bde57600080fd5b505af4158015610bf2573d6000803e3d6000fd5b50505050808a610c02919061166d565b9950505b80600003610c1657600060018f01555b600081118015610c265750600086115b15610c3957610c398e87868b8986611266565b8896505050505050505b979650505050505050565b6040516001600160a01b03821681527f93f45e69cdabde5c8296469e04ee6765b806aa9c3ef27883e43d1c3d227c4f229060200160405180910390a181546001600160a01b0319166001600160a01b0391909116179055565b6000610cb38383610408565b905080600003610cc257505050565b825442600160301b90910465ffffffffffff161115610cfc57825465ffffffffffff60601b1916600160601b4265ffffffffffff16021783555b6040516322fb941560e11b815273b916038b047c637bd2be297a7480708ba74e1a82906345f7282a90610d3790859030908690600401611680565b60006040518083038186803b158015610d4f57600080fd5b505af4158015610d63573d6000803e3d6000fd5b50505050505050565b600080610d7a86868661133e565b90506001836002811115610d9057610d906117a5565b148015610dad575060008480610da857610da8611643565b868809115b15610dc057610dbd60018261169f565b90505b90505b949350505050565b6000610dd88483856113f3565b90506000610dfd61271080610ded868961162c565b610df79190611659565b90611432565b6040805160008152602081018590529192506001600160a01b0388169130917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a37f57541f28ce268adbe5fa488e716321b8a31a74aa5461c983ad6293f322ebb3ea818784604051610e7a93929190611680565b60405180910390a150949350505050565b6007840154600090808203610ea4576000915050610dc3565b600986015460ff16610eb7579050610dc3565b60006018603c80896008015489610ece919061166d565b610ed89190611659565b610ee29190611659565b610eec9190611659565b9050610258811115610f02578492505050610dc3565b603c81118015610f1457506102588111155b15611110576000306001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7d91906117bb565b90506000610f8c82600a611796565b60018a01549091506000818810610fa35781610fa5565b875b90506000828911610fb65782610fb8565b885b90506000610fc6858361162c565b85610fd3856103e761162c565b610fdd919061162c565b610fe79190611659565b90506000610ff66001886117de565b61100190600a611796565b8361100c888761162c565b6110169190611659565b611020908861166d565b61102b90606361162c565b6110359190611659565b90506000611043828461169f565b90506000611052603c8b61166d565b905060005b611062601983611659565b8110156110c6576103e87d90e40fbeea1d3a4abc8955e946fe31cdcf66f634e1000000000000000000611096601986611796565b6110a09190611659565b6110aa908e61162c565b6110b49190611659565b9b506110bf816117f7565b9050611057565b5060005b6110d5601983611810565b811015611105576103e86110e9848e61162c565b6110f39190611659565b9b506110fe816117f7565b90506110ca565b505050505050505050505b5095945050505050565b60006001600160a01b038216611131575082610c43565b600061113e8785876113f3565b60405163c67e515160e01b815290915073b916038b047c637bd2be297a7480708ba74e1a829063c67e51519061117c908c9087908690600401611680565b60006040518083038186803b15801561119457600080fd5b505af41580156111a8573d6000803e3d6000fd5b5050505080856111b8919061169f565b6040805160008152602081018490529196506001600160a01b0385169130917fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7910160405180910390a3604080518981526001600160a01b038516602082015290810182905260608101879052608081018590527f3552e4b795fabf338ac18811c7db8508569032709558e19c88df5499681d7b139060a00160405180910390a15092979650505050505050565b8554600090600160301b900465ffffffffffff16428111156112a35761128c428261166d565b611296878761166d565b6112a0919061162c565b91505b6000836112b0898761162c565b6112ba908561169f565b6112c49190611659565b9050806112d5633b9aca008661162c565b6112df9190611659565b60018a01556112ee814261169f565b895471ffffffffffffffffffffffff0000000000001916600160301b65ffffffffffff9283160265ffffffffffff60601b191617600160601b429290921691909102179098555050505050505050565b60008080600019858709858702925082811083820303915050806000036113785783828161136e5761136e611643565b04925050506113ec565b80841161138457600080fd5b600084868809851960019081018716968790049682860381900495909211909303600082900391909104909201919091029190911760038402600290811880860282030280860282030280860282030280860282030280860282030280860290910302029150505b9392505050565b600080612710611403858761162c565b61140d9190611659565b905061099f8184816114216127108961162c565b61142b919061166d565b6001610d6c565b60008215611460578161144660018561166d565b6114509190611659565b61145b90600161169f565b611463565b60005b90505b92915050565b6000806040838503121561147f57600080fd5b50508035926020909101359150565b6000806000606084860312156114a357600080fd5b8335925060208401359150604084013565ffffffffffff811681146114c757600080fd5b809150509250925092565b803580151581146114e257600080fd5b919050565b600080604083850312156114fa57600080fd5b8235915061150a602084016114d2565b90509250929050565b60006020828403121561152557600080fd5b5035919050565b6000806040838503121561153f57600080fd5b8235915060208301356001600160a01b038116811461155d57600080fd5b809150509250929050565b600080600080600060a0868803121561158057600080fd5b853594506020860135935060408601359250606086013591506115a5608087016114d2565b90509295509295909350565b600080600080600080600060e0888a0312156115cc57600080fd5b505085359760208701359750604087013596606081013596506080810135955060a0810135945060c0013592509050565b60006020828403121561160f57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761146657611466611616565b634e487b7160e01b600052601260045260246000fd5b60008261166857611668611643565b500490565b8181038181111561146657611466611616565b9283526001600160a01b03919091166020830152604082015260600190565b8082018082111561146657611466611616565b600181815b808511156116ed5781600019048211156116d3576116d3611616565b808516156116e057918102915b93841c93908002906116b7565b509250929050565b60008261170457506001611466565b8161171157506000611466565b816001811461172757600281146117315761174d565b6001915050611466565b60ff84111561174257611742611616565b50506001821b611466565b5060208310610133831016604e8410600b8410161715611770575081810a611466565b61177a83836116b2565b806000190482111561178e5761178e611616565b029392505050565b600061146360ff8416836116f5565b634e487b7160e01b600052602160045260246000fd5b6000602082840312156117cd57600080fd5b815160ff811681146113ec57600080fd5b60ff828116828216039081111561146657611466611616565b60006001820161180957611809611616565b5060010190565b60008261181f5761181f611643565b50069056fea2646970667358221220e9a2087556b9a15c5aa603c30b196108a135a028fd6b22078e16bae992699c7964736f6c63430008110033
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 33 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.