Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "./vault/VaultRestricted.sol";
/**
* @notice Implementation of the {IVault} interface.
*
* @dev
* All vault instances are meant to be deployed via the Controller
* as a proxy and will not be recognizable by the Spool if they are
* not done so.
*
* The vault contract is capable of supporting a single currency underlying
* asset and deposit to multiple strategies at once, including dual-collateral
* ones.
*
* The vault also supports the additional distribution of extra reward tokens as
* an incentivization mechanism proportionate to each user's deposit amount within
* the vhe vault.
*
* Vault implementation consists of following contracts:
* 1. VaultImmutable: reads vault specific immutable variable from vault proxy contract
* 2. VaultBase: holds vault state variables and provides some of the common vault functions
* 3. RewardDrip: distributes vault incentivized rewards to users participating in the vault
* 4. VaultIndexActions: implements functions to synchronize the vault with central Spool contract
* 5. VaultRestricted: exposes functions restricted for other Spool specific contracts
* 6. Vault: exposes unrestricted functons to interact with the core vault functionality (deposit/withdraw/claim)
*/
contract Vault is VaultRestricted {
using SafeERC20 for IERC20;
using Bitwise for uint256;
/* ========== CONSTRUCTOR ========== */
/**
* @notice Sets the initial immutable values of the contract.
*
* @dev
* All values have been sanitized by the controller contract, meaning
* that no additional checks need to be applied here.
*
* @param _spool the spool implemenation
* @param _controller the controller implemenation
* @param _fastWithdraw fast withdraw implementation
* @param _feeHandler fee handler implementation
* @param _spoolOwner spool owner contract
*/
constructor(
ISpool _spool,
IController _controller,
IFastWithdraw _fastWithdraw,
IFeeHandler _feeHandler,
ISpoolOwner _spoolOwner
)
VaultBase(
_spool,
_controller,
_fastWithdraw,
_feeHandler
)
SpoolOwnable(_spoolOwner)
{}
/* ========== DEPOSIT ========== */
/**
* @notice Allows a user to perform a particular deposit to the vault.
*
* @dev
* Emits a {Deposit} event indicating the amount newly deposited for index.
*
* Perform redeem if possible:
* - Vault: Index has been completed (sync deposits/withdrawals)
* - User: Claim deposit shares or withdrawn amount
*
* Requirements:
*
* - the provided strategies must be valid
* - the caller must have pre-approved the contract for the token amount deposited
* - the caller cannot deposit zero value
* - the system should not be paused
*
* @param vaultStrategies strategies of this vault (verified internally)
* @param amount amount to deposit
* @param transferFromVault if the transfer should occur from the funds transfer(controller) address
*/
function deposit(address[] memory vaultStrategies, uint128 amount, bool transferFromVault)
external
verifyStrategies(vaultStrategies)
hasStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
redeemUserModifier
updateRewards
{
require(amount > 0, "NDP");
// get next possible index to deposit
uint24 activeGlobalIndex = _getActiveGlobalIndex();
// Mark user deposited amount for active index
vaultIndexAction[activeGlobalIndex].depositAmount += amount;
userIndexAction[msg.sender][activeGlobalIndex].depositAmount += amount;
// Mark vault strategies to deposit at index
_distributeInStrats(vaultStrategies, amount, activeGlobalIndex);
// mark that vault and user have interacted at this global index
_updateInteractedIndex(activeGlobalIndex);
_updateUserInteractedIndex(activeGlobalIndex);
// transfer user deposit to Spool
_transferDepositToSpool(amount, transferFromVault);
// store user deposit amount
_addInstantDeposit(amount);
emit Deposit(msg.sender, activeGlobalIndex, amount);
}
/**
* @notice Distributes a deposit to the various strategies based on the allocations of the vault.
*/
function _distributeInStrats(
address[] memory vaultStrategies,
uint128 amount,
uint256 activeGlobalIndex
) private {
uint128 amountLeft = amount;
uint256 lastElement = vaultStrategies.length - 1;
uint256 _proportions = proportions;
for (uint256 i; i < lastElement; i++) {
uint128 proportionateAmount = _getStrategyDepositAmount(_proportions, i, amount);
if (proportionateAmount > 0) {
spool.deposit(vaultStrategies[i], proportionateAmount, activeGlobalIndex);
amountLeft -= proportionateAmount;
}
}
if (amountLeft > 0) {
spool.deposit(vaultStrategies[lastElement], amountLeft, activeGlobalIndex);
}
}
/* ========== WITHDRAW ========== */
/**
* @notice Allows a user to withdraw their deposited funds from the vault at next possible index.
* The withdrawal is queued for when do hard work for index is completed.
*
* @dev
* Perform redeem if possible:
* - Vault: Index has been completed (sync deposits/withdrawals)
* - User: Claim deposit shares or withdrawn amount
*
* Emits a {Withdrawal} event indicating the shares burned, index of the withdraw and the amount of funds withdrawn.
*
* Requirements:
*
* - vault must not be reallocating
* - the provided strategies must be valid
* - the caller must have a non-zero amount of shares to withdraw
* - the caller must have enough shares to withdraw the specified share amount
* - the system should not be paused
*
* @param vaultStrategies strategies of this vault (verified internally)
* @param sharesToWithdraw shares amount to withdraw
* @param withdrawAll if all shares should be removed
*/
function withdraw(
address[] memory vaultStrategies,
uint128 sharesToWithdraw,
bool withdrawAll
)
external
verifyStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
noReallocation
redeemUserModifier
updateRewards
{
sharesToWithdraw = _withdrawShares(sharesToWithdraw, withdrawAll);
// get next possible index to withdraw
uint24 activeGlobalIndex = _getActiveGlobalIndex();
// mark user withdrawn shares amount for active index
userIndexAction[msg.sender][activeGlobalIndex].withdrawShares += sharesToWithdraw;
vaultIndexAction[activeGlobalIndex].withdrawShares += sharesToWithdraw;
// mark strategies in the spool contract to be withdrawn at next possible index
_withdrawFromStrats(vaultStrategies, sharesToWithdraw, activeGlobalIndex);
// mark that vault and user interacted at this global index
_updateInteractedIndex(activeGlobalIndex);
_updateUserInteractedIndex(activeGlobalIndex);
emit Withdraw(msg.sender, activeGlobalIndex, sharesToWithdraw);
}
/* ========== FAST WITHDRAW ========== */
/**
* @notice Allows a user to withdraw their deposited funds right away.
*
* @dev
* @dev
* User can execute the withdrawal of his shares from the vault at any time without
* waiting for the DHW to process it. This is done independently of other events (e.g. DHW)
* and the gas cost is paid entirely by the user.
* Shares belonging to the user and are sent back to the FastWithdraw contract
* where an actual withdrawal can be peformed, where user recieves the underlying tokens
* right away.
*
* Requirements:
*
* - vault must not be reallocating
* - the spool system must not be mid reallocation
* (started DHW and not finished, at index the reallocation was initiated)
* - the provided strategies must be valid
* - the sistem must not be in the middle of the reallocation
* - the system should not be paused
*
* @param vaultStrategies strategies of this vault
* @param sharesToWithdraw shares amount to withdraw
* @param withdrawAll if all shares should be removed
* @param fastWithdrawParams extra parameters to perform fast withdraw
*/
function withdrawFast(
address[] memory vaultStrategies,
uint128 sharesToWithdraw,
bool withdrawAll,
FastWithdrawParams memory fastWithdrawParams
)
external
noMidReallocation
verifyStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
noReallocation
redeemUserModifier
updateRewards
{
sharesToWithdraw = _withdrawShares(sharesToWithdraw, withdrawAll);
uint256 vaultShareProportion = _getVaultShareProportion(sharesToWithdraw);
totalShares -= sharesToWithdraw;
uint128[] memory strategyRemovedShares = spool.removeShares(vaultStrategies, vaultShareProportion);
uint256 proportionateDeposit = _getUserProportionateDeposit(sharesToWithdraw);
// transfer removed shares to fast withdraw contract
fastWithdraw.transferShares(
vaultStrategies,
strategyRemovedShares,
proportionateDeposit,
msg.sender,
fastWithdrawParams
);
emit WithdrawFast(msg.sender, sharesToWithdraw);
}
/**
* @dev Updates storage values according to shares withdrawn.
* If `withdrawAll` is true, all shares are removed from the users
* @param sharesToWithdraw Amount of shares to withdraw
* @param withdrawAll Withdraw all user shares
*/
function _withdrawShares(uint128 sharesToWithdraw, bool withdrawAll) private returns(uint128) {
User storage user = users[msg.sender];
uint128 userShares = user.shares;
uint128 userActiveInstantDeposit = user.instantDeposit;
// Substract the not processed instant deposit
// This way we don't consider the deposit that was not yet processed by the DHW
// when calculating amount of it withdrawn
LastIndexInteracted memory userIndexInteracted = userLastInteractions[msg.sender];
if (userIndexInteracted.index1 > 0) {
userActiveInstantDeposit -= userIndexAction[msg.sender][userIndexInteracted.index1].depositAmount;
// also check if user second index has pending actions
if (userIndexInteracted.index2 > 0) {
userActiveInstantDeposit -= userIndexAction[msg.sender][userIndexInteracted.index2].depositAmount;
}
}
// check if withdraw all flag was set or user requested
// withdraw of all shares in `sharesToWithdraw`
if (withdrawAll || (userShares > 0 && userShares == sharesToWithdraw)) {
sharesToWithdraw = userShares;
// set user shares to 0
user.shares = 0;
// substract all the users instant deposit processed till now
// substract the same amount from vault total instand deposit value
totalInstantDeposit -= userActiveInstantDeposit;
user.instantDeposit -= userActiveInstantDeposit;
} else {
require(
userShares >= sharesToWithdraw &&
sharesToWithdraw > 0,
"WSH"
);
// if we didnt withdraw all calculate the proportion of
// the instant deposit to substract it from the user and vault amounts
uint128 instantDepositWithdrawn = _getProportion128(userActiveInstantDeposit, sharesToWithdraw, userShares);
totalInstantDeposit -= instantDepositWithdrawn;
user.instantDeposit -= instantDepositWithdrawn;
// susrtact withdrawn shares from the user
// NOTE: vault shares will be substracted when the at the redeem
// for the current active index is processed. This way we substract it
// only once for all the users.
unchecked {
user.shares = userShares - sharesToWithdraw;
}
}
return sharesToWithdraw;
}
/**
* @notice Calculates user proportionate deposit when withdrawing and updated user deposit storage
* @dev Checks user index action to see if user already has some withdrawn shares
* pending to be processed.
* Called when performing the fast withdraw
*
* @param sharesToWithdraw shares amount to withdraw
*
* @return User deposit amount proportionate to the amount of shares being withdrawn
*/
function _getUserProportionateDeposit(uint128 sharesToWithdraw) private returns(uint256) {
User storage user = users[msg.sender];
LastIndexInteracted memory userIndexInteracted = userLastInteractions[msg.sender];
uint128 proportionateDeposit;
uint128 sharesAtWithdrawal = user.shares + sharesToWithdraw;
if (userIndexInteracted.index1 > 0) {
sharesAtWithdrawal += userIndexAction[msg.sender][userIndexInteracted.index1].withdrawShares;
if (userIndexInteracted.index2 > 0) {
sharesAtWithdrawal += userIndexAction[msg.sender][userIndexInteracted.index2].withdrawShares;
}
}
if (sharesAtWithdrawal > sharesToWithdraw) {
uint128 userTotalDeposit = user.activeDeposit;
proportionateDeposit = _getProportion128(userTotalDeposit, sharesToWithdraw, sharesAtWithdrawal);
user.activeDeposit = userTotalDeposit - proportionateDeposit;
} else {
proportionateDeposit = user.activeDeposit;
user.activeDeposit = 0;
}
return proportionateDeposit;
}
function _withdrawFromStrats(address[] memory vaultStrategies, uint128 totalSharesToWithdraw, uint256 activeGlobalIndex) private {
uint256 vaultShareProportion = _getVaultShareProportion(totalSharesToWithdraw);
for (uint256 i; i < vaultStrategies.length; i++) {
spool.withdraw(vaultStrategies[i], vaultShareProportion, activeGlobalIndex);
}
}
/* ========== CLAIM ========== */
/**
* @notice Allows a user to claim their debt from the vault after withdrawn shares were processed.
*
* @dev
* Fee is taken from the profit
* Perform redeem on user demand
*
* Emits a {DebtClaim} event indicating the debt the user claimed.
*
* Requirements:
*
* - if `doRedeemVault` is true, the provided strategies must be valid
* - the caller must have a non-zero debt owed
* - the system should not be paused (if doRedeemVault)
*
* @param doRedeemVault flag, to execute redeem for the vault (synchronize deposit/withdrawals with the system)
* @param vaultStrategies vault stratigies
* @param doRedeemUser flag, to execute redeem for the caller
*
* @return claimAmount amount of underlying asset, claimed by the caller
*/
function claim(
bool doRedeemVault,
address[] memory vaultStrategies,
bool doRedeemUser
) external returns (uint128 claimAmount) {
User storage user = users[msg.sender];
if (doRedeemVault) {
_verifyStrategies(vaultStrategies);
_redeemVaultStrategies(vaultStrategies);
}
if (doRedeemUser) {
_redeemUser();
}
claimAmount = user.owed;
require(claimAmount > 0, "CA0");
user.owed = 0;
// Calculate profit and take fees
uint128 userWithdrawnDeposits = user.withdrawnDeposits;
if (claimAmount > userWithdrawnDeposits) {
user.withdrawnDeposits = 0;
uint128 profit = claimAmount - userWithdrawnDeposits;
uint128 feesPaid = _payFeesAndTransfer(profit);
// Substract fees paid from claim amount
claimAmount -= feesPaid;
} else {
user.withdrawnDeposits = userWithdrawnDeposits - claimAmount;
}
_underlying().safeTransfer(msg.sender, claimAmount);
emit Claimed(msg.sender, claimAmount);
}
/* ========== REDEEM ========== */
/**
* @notice Redeem vault and user deposit and withdrawals
*
* Requirements:
*
* - the provided strategies must be valid
*
* @param vaultStrategies vault stratigies
*/
function redeemVaultAndUser(address[] memory vaultStrategies)
external
verifyStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
redeemUserModifier
{}
/**
* @notice Redeem vault and user and return the user state
* @dev Intended to be called as a static call for view purposes
*
* Requirements:
*
* - the provided strategies must be valid
*
* @param vaultStrategies vault strategies
*
* @return userShares current user shares
* @return activeDeposit user active deposit (already processed by the DHW)
* @return userOwed user total unclaimed amount
* @return userWithdrawnDeposits unclaimed withdrawn deposit amount
* @return userTotalUnderlying current user total underlying
* @return pendingDeposit1 pending user deposit for the next index
* @return pendingWithdrawalShares1 pending user withdrawal shares for the next index
* @return pendingDeposit2 pending user deposit for the index after the next one
* @return pendingWithdrawalShares2 pending user withdrawal shares for the after the next one
*/
function getUpdatedUser(address[] memory vaultStrategies)
external
returns (
uint256,
uint256,
uint256,
uint256,
uint256,
uint256,
uint256,
uint256,
uint256
)
{
(uint256 totalUnderlying, , , , , ) = getUpdatedVault(vaultStrategies);
_redeemUser();
User storage user = users[msg.sender];
uint256 userTotalUnderlying;
if (totalShares > 0 && user.shares > 0) {
userTotalUnderlying = (totalUnderlying * user.shares) / totalShares;
}
IndexAction storage indexAction1 = userIndexAction[msg.sender][userLastInteractions[msg.sender].index1];
IndexAction storage indexAction2 = userIndexAction[msg.sender][userLastInteractions[msg.sender].index2];
return (
user.shares,
user.activeDeposit, // amount of user deposited underlying token
user.owed, // underlying token claimable amount
user.withdrawnDeposits, // underlying token withdrawn amount
userTotalUnderlying,
indexAction1.depositAmount,
indexAction1.withdrawShares,
indexAction2.depositAmount,
indexAction2.withdrawShares
);
}
/**
* @notice Redeem vault strategy deposits and withdrawals after do hard work.
*
* Requirements:
*
* - the provided strategies must be valid
*
* @param vaultStrategies vault strategies
*/
function redeemVaultStrategies(address[] memory vaultStrategies)
external
verifyStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
{}
/**
* @notice Redeem vault strategy deposits and withdrawals after do hard work.
* @dev Intended to be called as a static call for view purposes
*
* Requirements:
*
* - the provided strategies must be valid
*
* @param vaultStrategies vault strategies
*
* @return totalUnderlying total vault underlying
* @return totalShares total vault shares
* @return pendingDeposit1 pending vault deposit for the next index
* @return pendingWithdrawalShares1 pending vault withdrawal shares for the next index
* @return pendingDeposit2 pending vault deposit for the index after the next one
* @return pendingWithdrawalShares2 pending vault withdrawal shares for the after the next one
*/
function getUpdatedVault(address[] memory vaultStrategies)
public
verifyStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
returns (
uint256,
uint256,
uint256,
uint256,
uint256,
uint256
)
{
uint256 totalUnderlying = 0;
for (uint256 i; i < vaultStrategies.length; i++) {
totalUnderlying += spool.getUnderlying(vaultStrategies[i]);
}
IndexAction storage indexAction1 = vaultIndexAction[lastIndexInteracted.index1];
IndexAction storage indexAction2 = vaultIndexAction[lastIndexInteracted.index2];
return (
totalUnderlying,
totalShares,
indexAction1.depositAmount,
indexAction1.withdrawShares,
indexAction2.depositAmount,
indexAction2.withdrawShares
);
}
/**
* @notice Redeem user deposits and withdrawals
*
* @dev Can only redeem user up to last index vault has redeemed
*/
function redeemUser()
external
{
_redeemUser();
}
/* ========== STRATEGY REMOVED ========== */
/**
* @notice Notify a vault a strategy was removed from the Spool system
* @dev
* This can be called by anyone after a strategy has been removed from the system.
* After the removal of the strategy that the vault contains, all actions
* calling central Spool contract will revert. This function must be called,
* to remove the strategy from the vault and update the strategy hash according
* to the new strategy array.
*
* Requirements:
*
* - The Spool system must finish reallocation if it's in progress
* - the provided strategies must be valid
* - The strategy must belong to this vault
* - The strategy must be removed from the system
*
* @param vaultStrategies Array of current vault strategies (including the removed one)
* @param i Index of the removed strategy in the `vaultStrategies`
*/
function notifyStrategyRemoved(
address[] memory vaultStrategies,
uint256 i
)
external
reallocationFinished
verifyStrategies(vaultStrategies)
hasStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
{
require(
i < vaultStrategies.length &&
!controller.validStrategy(vaultStrategies[i]),
"BSTR"
);
uint256 lastElement = vaultStrategies.length - 1;
address[] memory newStrategies = new address[](lastElement);
if (lastElement > 0) {
for (uint256 j; j < lastElement; j++) {
newStrategies[j] = vaultStrategies[j];
}
if (i < lastElement) {
newStrategies[i] = vaultStrategies[lastElement];
}
uint256 _proportions = proportions;
uint256 proportionsLeft = FULL_PERCENT - _proportions.get14BitUintByIndex(i);
if (lastElement > 1 && proportionsLeft > 0) {
if (i == lastElement) {
_proportions = _proportions.reset14BitUintByIndex(i);
} else {
uint256 lastProportion = _proportions.get14BitUintByIndex(lastElement);
_proportions = _proportions.reset14BitUintByIndex(i);
_proportions = _proportions.set14BitUintByIndex(i, lastProportion);
}
uint256 newProportions;
uint256 lastNewElement = lastElement - 1;
uint256 newProportionsLeft = FULL_PERCENT;
for (uint256 j; j < lastNewElement; j++) {
uint256 propJ = _proportions.get14BitUintByIndex(j);
propJ = (propJ * FULL_PERCENT) / proportionsLeft;
newProportions = newProportions.set14BitUintByIndex(j, propJ);
newProportionsLeft -= propJ;
}
newProportions = newProportions.set14BitUintByIndex(lastNewElement, newProportionsLeft);
proportions = newProportions;
} else {
proportions = FULL_PERCENT;
}
} else {
proportions = 0;
}
_updateStrategiesHash(newStrategies);
emit StrategyRemoved(i, vaultStrategies[i]);
}
/* ========== PRIVATE FUNCTIONS ========== */
/**
* @notice Throws if given array of strategies is empty
*/
function _hasStrategies(address[] memory vaultStrategies) private pure {
require(vaultStrategies.length > 0, "NST");
}
/* ========== MODIFIERS ========== */
/**
* @notice Throws if given array of strategies is empty
*/
modifier hasStrategies(address[] memory vaultStrategies) {
_hasStrategies(vaultStrategies);
_;
}
/**
* @notice Revert if reallocation is not finished for this vault
*/
modifier reallocationFinished() {
require(
!_isVaultReallocating() ||
reallocationIndex <= spool.getCompletedGlobalIndex(),
"RNF"
);
_;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 0;
uint256 private constant _ENTERED = 1;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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 `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @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);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (utils/Address.sol)
pragma solidity ^0.8.0;
/**
* @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
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toUint192(uint256 value) internal pure returns (uint192) {
require(value <= type(uint192).max, "SafeCast: value doesn't fit in 128 bits");
return uint192(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../external/@openzeppelin/token/ERC20/IERC20.sol";
interface IController {
/* ========== FUNCTIONS ========== */
function strategies(uint256 i) external view returns (address);
function validStrategy(address strategy) external view returns (bool);
function validVault(address vault) external view returns (bool);
function getStrategiesCount() external view returns(uint8);
function supportedUnderlying(IERC20 underlying)
external
view
returns (bool);
function getAllStrategies() external view returns (address[] memory);
function verifyStrategies(address[] calldata _strategies) external view;
function transferToSpool(
address transferFrom,
uint256 amount
) external;
function checkPaused() external view;
/* ========== EVENTS ========== */
event EmergencyWithdrawStrategy(address indexed strategy);
event EmergencyRecipientUpdated(address indexed recipient);
event EmergencyWithdrawerUpdated(address indexed withdrawer, bool set);
event PauserUpdated(address indexed user, bool set);
event UnpauserUpdated(address indexed user, bool set);
event VaultCreated(address indexed vault, address underlying, address[] strategies, uint256[] proportions,
uint16 vaultFee, address riskProvider, int8 riskTolerance);
event StrategyAdded(address strategy);
event StrategyRemoved(address strategy);
event VaultInvalid(address vault);
event DisableStrategy(address strategy);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "./ISwapData.sol";
struct FastWithdrawParams {
bool doExecuteWithdraw;
uint256[][] slippages;
SwapData[][] swapData;
}
interface IFastWithdraw {
function transferShares(
address[] calldata vaultStrategies,
uint128[] calldata sharesWithdrawn,
uint256 proportionateDeposit,
address user,
FastWithdrawParams calldata fastWithdrawParams
) external;
/* ========== EVENTS ========== */
event StrategyWithdrawn(address indexed user, address indexed vault, address indexed strategy);
event UserSharesSaved(address indexed user, address indexed vault);
event FastWithdrawExecuted(address indexed user, address indexed vault, uint256 totalWithdrawn);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../external/@openzeppelin/token/ERC20/IERC20.sol";
interface IFeeHandler {
function payFees(
IERC20 underlying,
uint256 profit,
address riskProvider,
address vaultOwner,
uint16 vaultFee
) external returns (uint256 feesPaid);
function setRiskProviderFee(address riskProvider, uint16 fee) external;
/* ========== EVENTS ========== */
event FeesPaid(address indexed vault, uint profit, uint ecosystemCollected, uint treasuryCollected, uint riskProviderColected, uint vaultFeeCollected);
event RiskProviderFeeUpdated(address indexed riskProvider, uint indexed fee);
event EcosystemFeeUpdated(uint indexed fee);
event TreasuryFeeUpdated(uint indexed fee);
event EcosystemCollectorUpdated(address indexed collector);
event TreasuryCollectorUpdated(address indexed collector);
event FeeCollected(address indexed collector, IERC20 indexed underlying, uint amount);
event EcosystemFeeCollected(IERC20 indexed underlying, uint amount);
event TreasuryFeeCollected(IERC20 indexed underlying, uint amount);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "./spool/ISpoolExternal.sol";
import "./spool/ISpoolReallocation.sol";
import "./spool/ISpoolDoHardWork.sol";
import "./spool/ISpoolStrategy.sol";
import "./spool/ISpoolBase.sol";
/// @notice Utility Interface for central Spool implementation
interface ISpool is ISpoolExternal, ISpoolReallocation, ISpoolDoHardWork, ISpoolStrategy, ISpoolBase {} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
interface ISpoolOwner {
function isSpoolOwner(address user) external view returns(bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
/**
* @notice Strict holding information how to swap the asset
* @member slippage minumum output amount
* @member path swap path, first byte represents an action (e.g. Uniswap V2 custom swap), rest is swap specific path
*/
struct SwapData {
uint256 slippage; // min amount out
bytes path; // 1st byte is action, then path
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
interface ISpoolBase {
/* ========== FUNCTIONS ========== */
function getCompletedGlobalIndex() external view returns(uint24);
function getActiveGlobalIndex() external view returns(uint24);
function isMidReallocation() external view returns (bool);
/* ========== EVENTS ========== */
event ReallocationTableUpdated(
uint24 indexed index,
bytes32 reallocationTableHash
);
event ReallocationTableUpdatedWithTable(
uint24 indexed index,
bytes32 reallocationTableHash,
uint256[][] reallocationTable
);
event DoHardWorkCompleted(uint24 indexed index);
event SetAllocationProvider(address actor, bool isAllocationProvider);
event SetIsDoHardWorker(address actor, bool isDoHardWorker);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
interface ISpoolDoHardWork {
/* ========== EVENTS ========== */
event DoHardWorkStrategyCompleted(address indexed strat, uint256 indexed index);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../ISwapData.sol";
interface ISpoolExternal {
/* ========== FUNCTIONS ========== */
function deposit(address strategy, uint128 amount, uint256 index) external;
function withdraw(address strategy, uint256 vaultProportion, uint256 index) external;
function fastWithdrawStrat(address strat, address underlying, uint256 shares, uint256[] calldata slippages, SwapData[] calldata swapData) external returns(uint128);
function redeem(address strat, uint256 index) external returns (uint128, uint128);
function redeemUnderlying(uint128 amount) external;
function redeemReallocation(address[] calldata vaultStrategies, uint256 depositProportions, uint256 index) external;
function removeShares(address[] calldata vaultStrategies, uint256 vaultProportion) external returns(uint128[] memory);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
interface ISpoolReallocation {
event StartReallocation(uint24 indexed index);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
interface ISpoolStrategy {
/* ========== FUNCTIONS ========== */
function getUnderlying(address strat) external returns (uint128);
function getVaultTotalUnderlyingAtIndex(address strat, uint256 index) external view returns(uint128);
function addStrategy(address strat) external;
function disableStrategy(address strategy, bool skipDisable) external;
function runDisableStrategy(address strategy) external;
function emergencyWithdraw(
address strat,
address withdrawRecipient,
uint256[] calldata data
) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../../external/@openzeppelin/token/ERC20/IERC20.sol";
interface IRewardDrip {
/* ========== STRUCTS ========== */
// The reward configuration struct, containing all the necessary data of a typical Synthetix StakingReward contract
struct RewardConfiguration {
uint32 rewardsDuration;
uint32 periodFinish;
uint192 rewardRate; // rewards per second multiplied by accuracy
uint32 lastUpdateTime;
uint224 rewardPerTokenStored;
mapping(address => uint256) userRewardPerTokenPaid;
mapping(address => uint256) rewards;
}
/* ========== FUNCTIONS ========== */
function getActiveRewards(address account) external;
function tokenBlacklist(IERC20 token) view external returns(bool);
/* ========== EVENTS ========== */
event RewardPaid(IERC20 token, address indexed user, uint256 reward);
event RewardAdded(IERC20 indexed token, uint256 amount, uint256 duration);
event RewardExtended(IERC20 indexed token, uint256 amount, uint256 leftover, uint256 duration, uint32 periodFinish);
event RewardRemoved(IERC20 indexed token);
event PeriodFinishUpdated(IERC20 indexed token, uint32 periodFinish);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "./IVaultDetails.sol";
interface IVaultBase {
/* ========== FUNCTIONS ========== */
function initialize(VaultInitializable calldata vaultInitializable) external;
/* ========== STRUCTS ========== */
struct User {
uint128 instantDeposit; // used for calculating rewards
uint128 activeDeposit; // users deposit after deposit process and claim
uint128 owed; // users owed underlying amount after withdraw has been processed and claimed
uint128 withdrawnDeposits; // users withdrawn deposit, used to calculate performance fees
uint128 shares; // users shares after deposit process and claim
}
/* ========== EVENTS ========== */
event Claimed(address indexed member, uint256 claimAmount);
event Deposit(address indexed member, uint256 indexed index, uint256 amount);
event Withdraw(address indexed member, uint256 indexed index, uint256 shares);
event WithdrawFast(address indexed member, uint256 shares);
event StrategyRemoved(uint256 i, address strategy);
event TransferVaultOwner(address owner);
event LowerVaultFee(uint16 fee);
event UpdateName(string name);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
struct VaultDetails {
address underlying;
address[] strategies;
uint256[] proportions;
address creator;
uint16 vaultFee;
address riskProvider;
int8 riskTolerance;
string name;
}
struct VaultInitializable {
string name;
address owner;
uint16 fee;
address[] strategies;
uint256[] proportions;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../../external/@openzeppelin/token/ERC20/IERC20.sol";
struct VaultImmutables {
IERC20 underlying;
address riskProvider;
int8 riskTolerance;
}
interface IVaultImmutable {
/* ========== FUNCTIONS ========== */
function underlying() external view returns (IERC20);
function riskProvider() external view returns (address);
function riskTolerance() external view returns (int8);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
interface IVaultIndexActions {
/* ========== STRUCTS ========== */
struct IndexAction {
uint128 depositAmount;
uint128 withdrawShares;
}
struct LastIndexInteracted {
uint128 index1;
uint128 index2;
}
struct Redeem {
uint128 depositShares;
uint128 withdrawnAmount;
}
/* ========== EVENTS ========== */
event VaultRedeem(uint indexed globalIndex);
event UserRedeem(address indexed member, uint indexed globalIndex);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
interface IVaultRestricted {
/* ========== FUNCTIONS ========== */
function reallocate(
address[] calldata vaultStrategies,
uint256 newVaultProportions,
uint256 finishedIndex,
uint24 activeIndex
) external returns (uint256[] memory, uint256);
function payFees(uint256 profit) external returns (uint256 feesPaid);
/* ========== EVENTS ========== */
event Reallocate(uint24 indexed index, uint256 newProportions);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
library Bitwise {
function get8BitUintByIndex(uint256 bitwiseData, uint256 i) internal pure returns(uint256) {
return (bitwiseData >> (8 * i)) & type(uint8).max;
}
// 14 bits is used for strategy proportions in a vault as FULL_PERCENT is 10_000
function get14BitUintByIndex(uint256 bitwiseData, uint256 i) internal pure returns(uint256) {
return (bitwiseData >> (14 * i)) & (16_383); // 16.383 is 2^14 - 1
}
function set14BitUintByIndex(uint256 bitwiseData, uint256 i, uint256 num14bit) internal pure returns(uint256) {
return bitwiseData + (num14bit << (14 * i));
}
function reset14BitUintByIndex(uint256 bitwiseData, uint256 i) internal pure returns(uint256) {
return bitwiseData & (~(16_383 << (14 * i)));
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
/**
* @notice Library to provide utils for hashing and hash compatison of Spool related data
*/
library Hash {
function hashReallocationTable(uint256[][] memory reallocationTable) internal pure returns(bytes32) {
return keccak256(abi.encode(reallocationTable));
}
function hashStrategies(address[] memory strategies) internal pure returns(bytes32) {
return keccak256(abi.encodePacked(strategies));
}
function sameStrategies(address[] memory strategies1, address[] memory strategies2) internal pure returns(bool) {
return hashStrategies(strategies1) == hashStrategies(strategies2);
}
function sameStrategies(address[] memory strategies, bytes32 strategiesHash) internal pure returns(bool) {
return hashStrategies(strategies) == strategiesHash;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
import "../external/@openzeppelin/utils/SafeCast.sol";
/**
* @notice A collection of custom math ustils used throughout the system
*/
library Math {
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? b : a;
}
function getProportion128(uint256 mul1, uint256 mul2, uint256 div) internal pure returns (uint128) {
return SafeCast.toUint128(((mul1 * mul2) / div));
}
function getProportion128Unchecked(uint256 mul1, uint256 mul2, uint256 div) internal pure returns (uint128) {
unchecked {
return uint128((mul1 * mul2) / div);
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../external/@openzeppelin/token/ERC20/IERC20.sol";
/// @title Common Spool contracts constants
abstract contract BaseConstants {
/// @dev 2 digits precision
uint256 internal constant FULL_PERCENT = 100_00;
/// @dev Accuracy when doing shares arithmetics
uint256 internal constant ACCURACY = 10**30;
}
/// @title Contains USDC token related values
abstract contract USDC {
/// @notice USDC token contract address
IERC20 internal constant USDC_ADDRESS = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../interfaces/ISpoolOwner.sol";
/// @title Logic to help check whether the caller is the Spool owner
abstract contract SpoolOwnable {
/// @notice Contract that checks if address is Spool owner
ISpoolOwner internal immutable spoolOwner;
/**
* @notice Sets correct initial values
* @param _spoolOwner Spool owner contract address
*/
constructor(ISpoolOwner _spoolOwner) {
require(
address(_spoolOwner) != address(0),
"SpoolOwnable::constructor: Spool owner contract address cannot be 0"
);
spoolOwner = _spoolOwner;
}
/**
* @notice Checks if caller is Spool owner
* @return True if caller is Spool owner, false otherwise
*/
function isSpoolOwner() internal view returns(bool) {
return spoolOwner.isSpoolOwner(msg.sender);
}
/// @notice Checks and throws if caller is not Spool owner
function _onlyOwner() private view {
require(isSpoolOwner(), "SpoolOwnable::onlyOwner: Caller is not the Spool owner");
}
/// @notice Checks and throws if caller is not Spool owner
modifier onlyOwner() {
_onlyOwner();
_;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
import "../interfaces/IController.sol";
/// @title Facilitates checking if the system is paused or not
abstract contract SpoolPausable {
/* ========== STATE VARIABLES ========== */
/// @notice The controller contract that is consulted for a strategy's and vault's validity
IController public immutable controller;
/**
* @notice Sets initial values
* @param _controller Controller contract address
*/
constructor(IController _controller) {
require(
address(_controller) != address(0),
"SpoolPausable::constructor: Controller contract address cannot be 0"
);
controller = _controller;
}
/* ========== MODIFIERS ========== */
/// @notice Throws if system is paused
modifier systemNotPaused() {
controller.checkPaused();
_;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../interfaces/vault/IRewardDrip.sol";
import "./VaultBase.sol";
import "../external/@openzeppelin/utils/SafeCast.sol";
import "../external/@openzeppelin/security/ReentrancyGuard.sol";
import "../libraries/Math.sol";
/**
* @notice Implementation of the {IRewardDrip} interface.
*
* @dev
* An adaptation of the Synthetix StakingRewards contract to support multiple tokens:
*
* https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol
*
* Instead of storing the values of the StakingRewards contract at the contract level,
* they are stored in a struct that is mapped to depending on the reward token instead.
*/
abstract contract RewardDrip is IRewardDrip, ReentrancyGuard, VaultBase {
using SafeERC20 for IERC20;
/* ========== CONSTANTS ========== */
/// @notice Multiplier used when dealing reward calculations
uint256 constant private REWARD_ACCURACY = 1e18;
/* ========== STATE VARIABLES ========== */
/// @notice All reward tokens supported by the contract
mapping(uint256 => IERC20) public rewardTokens;
/// @notice Vault reward token incentive configuration
mapping(IERC20 => RewardConfiguration) public rewardConfiguration;
/// @notice Blacklisted force-removed tokens
mapping(IERC20 => bool) public override tokenBlacklist;
/* ========== VIEWS ========== */
function lastTimeRewardApplicable(IERC20 token)
public
view
returns (uint32)
{
return uint32(Math.min(block.timestamp, rewardConfiguration[token].periodFinish));
}
function rewardPerToken(IERC20 token) public view returns (uint224) {
RewardConfiguration storage config = rewardConfiguration[token];
if (totalInstantDeposit == 0)
return config.rewardPerTokenStored;
uint256 timeDelta = lastTimeRewardApplicable(token) - config.lastUpdateTime;
if (timeDelta == 0)
return config.rewardPerTokenStored;
return
SafeCast.toUint224(
config.rewardPerTokenStored +
((timeDelta
* config.rewardRate)
/ totalInstantDeposit)
);
}
function earned(IERC20 token, address account)
public
view
returns (uint256)
{
RewardConfiguration storage config = rewardConfiguration[token];
uint256 userShares = users[account].instantDeposit;
if (userShares == 0)
return config.rewards[account];
uint256 userRewardPerTokenPaid = config.userRewardPerTokenPaid[account];
return
((userShares *
(rewardPerToken(token) - userRewardPerTokenPaid))
/ REWARD_ACCURACY)
+ config.rewards[account];
}
function getRewardForDuration(IERC20 token)
external
view
returns (uint256)
{
RewardConfiguration storage config = rewardConfiguration[token];
return uint256(config.rewardRate) * config.rewardsDuration;
}
/* ========== MUTATIVE FUNCTIONS ========== */
function getRewards(IERC20[] memory tokens) external nonReentrant {
for (uint256 i; i < tokens.length; i++) {
_getReward(tokens[i], msg.sender);
}
}
function getActiveRewards(address account) external override onlyController nonReentrant {
uint256 _rewardTokensCount = rewardTokensCount;
for (uint256 i; i < _rewardTokensCount; i++) {
_getReward(rewardTokens[i], account);
}
}
function _getReward(IERC20 token, address account)
internal
updateReward(token, account)
{
RewardConfiguration storage config = rewardConfiguration[token];
require(
config.rewardsDuration != 0,
"BTK"
);
uint256 reward = config.rewards[account];
if (reward > 0) {
config.rewards[account] = 0;
token.safeTransfer(account, reward);
emit RewardPaid(token, account, reward);
}
}
/* ========== RESTRICTED FUNCTIONS ========== */
/**
* @notice Allows a new token to be added to the reward system
*
* @dev
* Emits an {TokenAdded} event indicating the newly added reward token
* and configuration
*
* Requirements:
*
* - the caller must be the reward distributor
* - the reward duration must be non-zero
* - the token must not have already been added
*
*/
function addToken(
IERC20 token,
uint32 rewardsDuration,
uint256 reward
) external onlyVaultOwnerOrSpoolOwner exceptUnderlying(token) {
RewardConfiguration storage config = rewardConfiguration[token];
require(!tokenBlacklist[token], "TOBL");
require(
rewardsDuration != 0 &&
config.lastUpdateTime == 0,
"BCFG"
);
require(
rewardTokensCount <= 5,
"TMAX"
);
rewardTokens[rewardTokensCount] = token;
rewardTokensCount++;
config.rewardsDuration = rewardsDuration;
if (reward > 0) {
_notifyRewardAmount(token, reward);
}
}
function notifyRewardAmount(IERC20 token, uint256 reward, uint32 rewardsDuration)
external
onlyVaultOwnerOrSpoolOwner
{
rewardConfiguration[token].rewardsDuration = rewardsDuration;
_notifyRewardAmount(token, reward);
}
function _notifyRewardAmount(IERC20 token, uint256 reward)
private
updateReward(token, address(0))
{
RewardConfiguration storage config = rewardConfiguration[token];
require(
config.rewardPerTokenStored + (reward * REWARD_ACCURACY) <= type(uint192).max,
"RTB"
);
token.safeTransferFrom(msg.sender, address(this), reward);
uint32 newPeriodFinish = uint32(block.timestamp) + config.rewardsDuration;
if (block.timestamp >= config.periodFinish) {
config.rewardRate = SafeCast.toUint192((reward * REWARD_ACCURACY) / config.rewardsDuration);
emit RewardAdded(token, reward, config.rewardsDuration);
} else {
// If extending or adding additional rewards,
// cannot set new finish time to be less than previously configured
require(config.periodFinish <= newPeriodFinish, "PFS");
uint256 remaining = config.periodFinish - block.timestamp;
uint256 leftover = remaining * config.rewardRate;
uint192 newRewardRate = SafeCast.toUint192((reward * REWARD_ACCURACY + leftover) / config.rewardsDuration);
require(
newRewardRate >= config.rewardRate,
"LRR"
);
config.rewardRate = newRewardRate;
emit RewardExtended(token, reward, leftover, config.rewardsDuration, newPeriodFinish);
}
config.lastUpdateTime = uint32(block.timestamp);
config.periodFinish = newPeriodFinish;
}
// End rewards emission earlier
function updatePeriodFinish(IERC20 token, uint32 timestamp)
external
onlyOwner
updateReward(token, address(0))
{
if (rewardConfiguration[token].lastUpdateTime > timestamp) {
rewardConfiguration[token].periodFinish = rewardConfiguration[token].lastUpdateTime;
} else {
rewardConfiguration[token].periodFinish = timestamp;
}
emit PeriodFinishUpdated(token, rewardConfiguration[token].periodFinish);
}
/**
* @notice Claim reward tokens
* @dev
* This is meant to be an emergency function to claim reward tokens.
* Users that have not claimed yet will not be able to claim as
* the rewards will be removed.
*
* Requirements:
*
* - the caller must be Spool DAO
* - cannot claim vault underlying token
* - cannot only execute if the reward finished
*
* @param token Token address to remove
* @param amount Amount of tokens to claim
*/
function claimFinishedRewards(IERC20 token, uint256 amount) external onlyOwner exceptUnderlying(token) onlyFinished(token) {
token.safeTransfer(msg.sender, amount);
}
/**
* @notice Force remove reward from vault rewards configuration.
* @dev This is meant to be an emergency function if a reward token breaks.
*
* Requirements:
*
* - the caller must be Spool DAO
*
* @param token Token address to remove
*/
function forceRemoveReward(IERC20 token) external onlyOwner {
tokenBlacklist[token] = true;
_removeReward(token);
delete rewardConfiguration[token];
}
/**
* @notice Remove reward from vault rewards configuration.
* @dev
* Used to sanitize vault and save on gas, after the reward has ended.
* Users will be able to claim rewards
*
* Requirements:
*
* - the caller must be the spool owner or Spool DAO
* - cannot claim vault underlying token
* - cannot only execute if the reward finished
*
* @param token Token address to remove
*/
function removeReward(IERC20 token)
external
onlyVaultOwnerOrSpoolOwner
onlyFinished(token)
updateReward(token, address(0))
{
_removeReward(token);
}
/* ========== PRIVATE FUNCTIONS ========== */
/**
* @notice Syncs rewards across all tokens of the system
*
* This function is meant to be invoked every time the instant deposit
* of a user changes.
*/
function _updateRewards(address account) private {
uint256 _rewardTokensCount = rewardTokensCount;
for (uint256 i; i < _rewardTokensCount; i++)
_updateReward(rewardTokens[i], account);
}
function _updateReward(IERC20 token, address account) private {
RewardConfiguration storage config = rewardConfiguration[token];
config.rewardPerTokenStored = rewardPerToken(token);
config.lastUpdateTime = lastTimeRewardApplicable(token);
if (account != address(0)) {
config.rewards[account] = earned(token, account);
config.userRewardPerTokenPaid[account] = config
.rewardPerTokenStored;
}
}
function _removeReward(IERC20 token) private {
uint256 _rewardTokensCount = rewardTokensCount;
for (uint256 i; i < _rewardTokensCount; i++) {
if (rewardTokens[i] == token) {
rewardTokens[i] = rewardTokens[_rewardTokensCount - 1];
delete rewardTokens[_rewardTokensCount - 1];
rewardTokensCount--;
emit RewardRemoved(token);
break;
}
}
}
function _exceptUnderlying(IERC20 token) private view {
require(
token != _underlying(),
"NUT"
);
}
function _onlyFinished(IERC20 token) private view {
require(
block.timestamp > rewardConfiguration[token].periodFinish,
"RNF"
);
}
/**
* @notice Ensures that the caller is the controller
*/
function _onlyController() private view {
require(
msg.sender == address(controller),
"OCTRL"
);
}
/* ========== MODIFIERS ========== */
modifier updateReward(IERC20 token, address account) {
_updateReward(token, account);
_;
}
modifier updateRewards() {
_updateRewards(msg.sender);
_;
}
modifier exceptUnderlying(IERC20 token) {
_exceptUnderlying(token);
_;
}
modifier onlyFinished(IERC20 token) {
_onlyFinished(token);
_;
}
/**
* @notice Throws if called by anyone else other than the controller
*/
modifier onlyController() {
_onlyController();
_;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
// libraries
import "../external/@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import "../external/@openzeppelin/utils/SafeCast.sol";
import "../libraries/Bitwise.sol";
import "../libraries/Hash.sol";
// extends
import "../interfaces/vault/IVaultBase.sol";
import "./VaultImmutable.sol";
import "../shared/SpoolOwnable.sol";
import "../shared/Constants.sol";
// other imports
import "../interfaces/vault/IVaultDetails.sol";
import "../interfaces/ISpool.sol";
import "../interfaces/IController.sol";
import "../interfaces/IFastWithdraw.sol";
import "../interfaces/IFeeHandler.sol";
import "../shared/SpoolPausable.sol";
/**
* @notice Implementation of the {IVaultBase} interface.
*
* @dev
* Vault base holds vault state variables and provides some of the common vault functions.
*/
abstract contract VaultBase is IVaultBase, VaultImmutable, SpoolOwnable, SpoolPausable, BaseConstants {
using Bitwise for uint256;
using SafeERC20 for IERC20;
/* ========== STATE VARIABLES ========== */
/// @notice The central Spool contract
ISpool internal immutable spool;
/// @notice The fast withdraw contract
IFastWithdraw internal immutable fastWithdraw;
/// @notice The fee handler contract
IFeeHandler internal immutable feeHandler;
/// @notice Boolean signaling if the contract was initialized yet
bool private _initialized;
/// @notice The owner of the vault, also the vault fee recipient
address public vaultOwner;
/// @notice Vault owner fee
uint16 public vaultFee;
/// @notice The name of the vault
string public name;
/// @notice The total shares of a vault
uint128 public totalShares;
/// @notice Total instant deposit, used to calculate vault reward incentives
uint128 public totalInstantDeposit;
/// @notice The proportions of each strategy when depositing
/// @dev Proportions are 14bits each, and the add up to FULL_PERCENT (10.000)
uint256 public proportions;
/// @notice Proportions to deposit after reallocation withdraw amount is claimed
uint256 internal depositProportions;
/// @notice Hash of the strategies list
bytes32 public strategiesHash;
/// @notice Number of vault incentivized tokens
uint8 public rewardTokensCount;
/// @notice Data if vault and at what index vault is reallocating
uint24 public reallocationIndex;
/// @notice User vault state values
mapping(address => User) public users;
/* ========== CONSTRUCTOR ========== */
/**
* @notice Sets the initial immutable values of the contract common for all vaults.
*
* @dev
* All values have been sanitized by the controller contract, meaning
* that no additional checks need to be applied here.
*
* @param _spool the spool implemenation
* @param _controller the controller implementation
* @param _fastWithdraw fast withdraw implementation
* @param _feeHandler fee handler implementation
*/
constructor(
ISpool _spool,
IController _controller,
IFastWithdraw _fastWithdraw,
IFeeHandler _feeHandler
)
SpoolPausable(_controller)
{
require(address(_spool) != address(0), "VaultBase::constructor: Spool address cannot be 0");
require(address(_fastWithdraw) != address(0), "VaultBase::constructor: FastWithdraw address cannot be 0");
require(address(_feeHandler) != address(0), "VaultBase::constructor: Fee Handler address cannot be 0");
spool = _spool;
fastWithdraw = _fastWithdraw;
feeHandler = _feeHandler;
}
/* ========== INITIALIZE ========== */
/**
* @notice Initializes state of the vault at proxy creation.
* @dev Called only once by vault factory after deploying a vault proxy.
* All values have been sanitized by the controller contract, meaning
* that no additional checks need to be applied here.
*
* @param vaultInitializable initial vault specific variables
*/
function initialize(
VaultInitializable memory vaultInitializable
) external override initializer {
vaultOwner = vaultInitializable.owner;
vaultFee = vaultInitializable.fee;
name = vaultInitializable.name;
proportions = _mapProportionsArrayToBits(vaultInitializable.proportions);
_updateStrategiesHash(vaultInitializable.strategies);
}
/* ========== VIEW FUNCTIONS ========== */
/**
* @notice Calculate and return proportion of passed parameters of 128 bit size
* @dev Calculates the value using in 256 bit space, later casts back to 128 bit
* Requirements:
*
* - the result can't be bigger than maximum 128 bits value
*
* @param mul1 first multiplication value
* @param mul2 second multiplication value
* @param div result division value
*
* @return 128 bit proportion result
*/
function _getProportion128(uint128 mul1, uint128 mul2, uint128 div) internal pure returns (uint128) {
return SafeCast.toUint128((uint256(mul1) * mul2) / div);
}
/* ========== RESTRICTED FUNCTIONS ========== */
/**
* @notice Transfer vault owner to another address.
*
* @param _vaultOwner new vault owner address
*
* Requirements:
*
* - the caller can only be the vault owner or Spool DAO
*/
function transferVaultOwner(address _vaultOwner) external onlyVaultOwnerOrSpoolOwner {
vaultOwner = _vaultOwner;
emit TransferVaultOwner(_vaultOwner);
}
/**
* @notice Set lower vault fee.
*
* @param _vaultFee new vault fee
*
* Requirements:
*
* - the caller can only be the vault owner
* - new vault fee must be lower than before
*/
function lowerVaultFee(uint16 _vaultFee) external {
require(
msg.sender == vaultOwner &&
_vaultFee < vaultFee,
"FEE"
);
vaultFee = _vaultFee;
emit LowerVaultFee(_vaultFee);
}
/**
* @notice Update the name of the vault.
*
* @param _name new vault name
*
* Requirements:
*
* - the caller can only be the Spool DAO
*/
function updateName(string memory _name) external onlyOwner {
name = _name;
emit UpdateName(_name);
}
// =========== DEPOSIT HELPERS ============ //
/**
* @notice Update instant deposit user and vault amounts
*
* @param amount deposited amount
*/
function _addInstantDeposit(uint128 amount) internal {
users[msg.sender].instantDeposit += amount;
totalInstantDeposit += amount;
}
/**
* @notice Get strategy deposit amount for the strategy
* @param _proportions Vault strategy proportions (14bit each)
* @param i index to get the proportion
* @param amount Total deposit amount
* @return strategyDepositAmount
*/
function _getStrategyDepositAmount(
uint256 _proportions,
uint256 i,
uint256 amount
) internal pure returns (uint128) {
return SafeCast.toUint128((_proportions.get14BitUintByIndex(i) * amount) / FULL_PERCENT);
}
/**
* @notice Transfers deposited underlying asset amount from user to spool contract.
* @dev Transfer happens from the vault or controller, defined by the user
*
* @param amount deposited amount
* @param fromVault flag indicating wether the transfer is intiafed from the vault or controller
*/
function _transferDepositToSpool(uint128 amount, bool fromVault) internal {
if (fromVault) {
_underlying().safeTransferFrom(msg.sender, address(spool), amount);
} else {
controller.transferToSpool(msg.sender, amount);
}
}
/* ========== WITHDRAW HELPERS ========== */
/**
* @notice Calculates proportions of shares relative to the total shares
* @dev Value has accuracy of `ACCURACY` which is 10^30
*
* @param sharesToWithdraw amount of shares
*
* @return total vault shares proportion
*/
function _getVaultShareProportion(uint128 sharesToWithdraw) internal view returns(uint256) {
return (ACCURACY * sharesToWithdraw) / totalShares;
}
// =========== PERFORMANCE FEES ============ //
/**
* @notice Pay fees to fee handler contract and transfer fee amount.
*
* @param profit Total profit made by the users
* @return feeSize Fee amount calculated from profit
*/
function _payFeesAndTransfer(uint256 profit) internal returns (uint128 feeSize) {
feeSize = SafeCast.toUint128(_payFees(profit));
_underlying().safeTransfer(address(feeHandler), feeSize);
}
/**
* @notice Call fee handler contract to pay fees, without transfering assets
* @dev Fee handler updates the fee storage slots and returns
*
* @param profit Total profit made by the users
* @return Fee amount calculated from profit
*/
function _payFees(uint256 profit) internal returns (uint256) {
return feeHandler.payFees(
_underlying(),
profit,
_riskProvider(),
vaultOwner,
vaultFee
);
}
// =========== STRATEGIIES ============ //
/**
* @notice Map vault strategy proportions array in one uint256 word.
*
* @dev Proportions sum up to `FULL_PERCENT` (10_000).
* There is maximum of 18 elements, and each takes maximum of 14bits.
*
* @param _proportions Vault strategy proportions array
* @return Mapped propportion 256 bit word format
*/
function _mapProportionsArrayToBits(uint256[] memory _proportions) internal pure returns (uint256) {
uint256 proportions14bit;
for (uint256 i = 0; i < _proportions.length; i++) {
proportions14bit = proportions14bit.set14BitUintByIndex(i, _proportions[i]);
}
return proportions14bit;
}
/**
* @dev Store vault strategy addresses array hash in `strategiesHash` storage
* @param vaultStrategies Array of strategy addresses
*/
function _updateStrategiesHash(address[] memory vaultStrategies) internal {
strategiesHash = Hash.hashStrategies(vaultStrategies);
}
/**
* @dev verify vault strategy addresses array against storage `strategiesHash`
* @param vaultStrategies Array of strategies to verify
*/
function _verifyStrategies(address[] memory vaultStrategies) internal view {
require(Hash.sameStrategies(vaultStrategies, strategiesHash), "VSH");
}
/* ========== PRIVATE FUNCTIONS ========== */
/**
* @notice Verify the caller is The vault owner or Spool DAO
*
* @dev
* Only callable from onlyVaultOwnerOrSpoolOwner modifier.
*
* Requirements:
*
* - msg.sender is the vault owner or Spool DAO
*/
function _onlyVaultOwnerOrSpoolOwner() private view {
require(
msg.sender == vaultOwner || isSpoolOwner(),
"OOD"
);
}
/**
* @notice Verify the caller is the spool contact
*
* @dev
* Only callable from onlySpool modifier.
*
* Requirements:
*
* - msg.sender is central spool contract
*/
function _onlySpool() private view {
require(address(spool) == msg.sender, "OSP");
}
/**
* @notice Verify caller is the spool contact
*
* @dev
* Only callable from onlyFastWithdraw modifier.
*
* Requirements:
*
* - caller is fast withdraw contract
*/
function _onlyFastWithdraw() private view {
require(address(fastWithdraw) == msg.sender, "OFW");
}
/**
* @notice Dissallow action if Spool reallocation already started
*/
function _noMidReallocation() private view {
require(!spool.isMidReallocation(), "NMR");
}
/* ========== MODIFIERS ========== */
/**
* @notice Ensures caller is vault owner or spool owner.
*/
modifier onlyVaultOwnerOrSpoolOwner() {
_onlyVaultOwnerOrSpoolOwner();
_;
}
/**
* @notice Ensures caller is central spool contract
*/
modifier onlySpool() {
_onlySpool();
_;
}
/**
* @notice Ensures caller is fast withdraw contract
*/
modifier onlyFastWithdraw() {
_onlyFastWithdraw();
_;
}
/**
* @notice Verifies given array of strategy addresses
*/
modifier verifyStrategies(address[] memory vaultStrategies) {
_verifyStrategies(vaultStrategies);
_;
}
/**
* @notice Ensures the system is not mid reallocation
*/
modifier noMidReallocation() {
_noMidReallocation();
_;
}
/**
* @notice Ensures the vault has not been initialized before
*/
modifier initializer() {
require(!_initialized, "AINT");
_;
_initialized = true;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../interfaces/vault/IVaultImmutable.sol";
/**
* @notice This contracts calls vault proxy that stores following
* properties as immutables.
*/
abstract contract VaultImmutable {
/* ========== FUNCTIONS ========== */
/**
* @dev Returns the underlying vault token from proxy address
* @return Underlying token contract
*/
function _underlying() internal view returns (IERC20) {
return IVaultImmutable(address(this)).underlying();
}
/**
* @dev Returns vaults risk provider from proxy address
* @return Risk provider contract
*/
function _riskProvider() internal view returns (address) {
return IVaultImmutable(address(this)).riskProvider();
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../interfaces/vault/IVaultIndexActions.sol";
import "./RewardDrip.sol";
/**
* @notice VaultIndexActions extends VaultBase and holds the logic to process index related data and actions.
*
* @dev
* Index functions are executed when state changes are performed, to synchronize to vault with central Spool contract.
*
* Index actions include:
* - Redeem vault: claiming vault shares and withdrawn amount when DHW is complete
* - Redeem user: claiming user deposit shares and/or withdrawn amount after vault claim has been processed
*/
abstract contract VaultIndexActions is IVaultIndexActions, RewardDrip {
using SafeERC20 for IERC20;
using Bitwise for uint256;
/* ========== CONSTANTS ========== */
/// @notice Value to multiply new deposit recieved to get the share amount
uint128 private constant SHARES_MULTIPLIER = 10**6;
/// @notice number of locked shares when initial shares are added
/// @dev This is done to prevent rounding errors and share manipulation
uint128 private constant INITIAL_SHARES_LOCKED = 10**11;
/// @notice minimum shares size to avoid loss of share due to computation precision
/// @dev If total shares go unders this value, new deposit is multiplied by the `SHARES_MULTIPLIER` again
uint256 private constant MIN_SHARES_FOR_ACCURACY = INITIAL_SHARES_LOCKED * 10;
/* ========== STATE VARIABLES ========== */
/// @notice Holds up to 2 global indexes vault last interacted at and havent been redeemed yet
/// @dev Second index can only be the next index of the first one
/// Second index is used if the do-hard-work is executed in 2 transactions and actions are executed in between
LastIndexInteracted public lastIndexInteracted;
/// @notice Maps all vault actions to the corresponding global index
mapping(uint256 => IndexAction) public vaultIndexAction;
/// @notice Maps user actions to the corresponding global index
mapping(address => mapping(uint256 => IndexAction)) public userIndexAction;
/// @notice Holds up to 2 global indexes users last interacted with, and havent been redeemed yet
mapping(address => LastIndexInteracted) public userLastInteractions;
/// @notice Global index to deposit and withdraw vault redeem
mapping(uint256 => Redeem) public redeems;
// =========== VIEW FUNCTIONS ============ //
/**
* @notice Checks and sets the "is reallocating" flag for given index
* @param index Index to check
* @return isReallocating True if vault is reallocating at this `index`
*/
function _isVaultReallocatingAtIndex(uint256 index) internal view returns (bool isReallocating) {
if (index == reallocationIndex) {
isReallocating = true;
}
}
/**
* @notice Check if the vault is set to reallocate
* @dev True if in the current index or the next one
* @return isReallocating True if vault is set to reallocate
*/
function _isVaultReallocating() internal view returns (bool isReallocating) {
if (reallocationIndex > 0) {
isReallocating = true;
}
}
// =========== VAULT REDEEM ============ //
/**
* @notice Redeem vault strategies after do hard work (DHW) has been completed
*
* @dev
* This is only possible if all vault strategy DHWs have been executed, otherwise it's reverted.
* If the system is paused, function will revert - impacts vault functions deposit, withdraw, fastWithdraw,
* claim, reallocate.
* @param vaultStrategies strategies of this vault (verified internally)
*/
function _redeemVaultStrategies(address[] memory vaultStrategies) internal systemNotPaused {
LastIndexInteracted memory _lastIndexInteracted = lastIndexInteracted;
if (_lastIndexInteracted.index1 > 0) {
uint256 globalIndex1 = _lastIndexInteracted.index1;
uint256 completedGlobalIndex = spool.getCompletedGlobalIndex();
if (globalIndex1 <= completedGlobalIndex) {
// redeem interacted index 1
_redeemStrategiesIndex(globalIndex1, vaultStrategies);
_lastIndexInteracted.index1 = 0;
if (_lastIndexInteracted.index2 > 0) {
uint256 globalIndex2 = _lastIndexInteracted.index2;
if (globalIndex2 <= completedGlobalIndex) {
// redeem interacted index 2
_redeemStrategiesIndex(globalIndex2, vaultStrategies);
} else {
_lastIndexInteracted.index1 = _lastIndexInteracted.index2;
}
_lastIndexInteracted.index2 = 0;
}
lastIndexInteracted = _lastIndexInteracted;
}
}
}
/**
* @notice Redeem strategies for at index
* @dev Causes additional gas for first interaction after DHW index has been completed
* @param globalIndex Global index
* @param vaultStrategies Array of vault strategy addresses
*/
function _redeemStrategiesIndex(uint256 globalIndex, address[] memory vaultStrategies) private {
uint128 _totalShares = totalShares;
uint128 totalReceived = 0;
uint128 totalWithdrawn = 0;
uint128 totalUnderlyingAtIndex = 0;
// if vault was reallocating at index claim reallocation deposit
bool isReallocating = _isVaultReallocatingAtIndex(globalIndex);
if (isReallocating) {
spool.redeemReallocation(vaultStrategies, depositProportions, globalIndex);
// Reset reallocation index to 0
reallocationIndex = 0;
}
// go over strategies and redeem deposited shares and withdrawn amount
for (uint256 i = 0; i < vaultStrategies.length; i++) {
address strat = vaultStrategies[i];
(uint128 receivedTokens, uint128 withdrawnTokens) = spool.redeem(strat, globalIndex);
totalReceived += receivedTokens;
totalWithdrawn += withdrawnTokens;
totalUnderlyingAtIndex += spool.getVaultTotalUnderlyingAtIndex(strat, globalIndex);
}
// redeem underlying withdrawn token for all strategies at once
if (totalWithdrawn > 0) {
spool.redeemUnderlying(totalWithdrawn);
}
// substract withdrawn shares
_totalShares -= vaultIndexAction[globalIndex].withdrawShares;
// calculate new deposit shares
uint128 newShares = 0;
if (_totalShares <= MIN_SHARES_FOR_ACCURACY || totalUnderlyingAtIndex == 0) {
// Enforce minimum shares size to avoid loss of share due to computation precision
newShares = totalReceived * SHARES_MULTIPLIER;
if (_totalShares < INITIAL_SHARES_LOCKED) {
if (newShares + _totalShares >= INITIAL_SHARES_LOCKED) {
unchecked {
uint128 newLockedShares = INITIAL_SHARES_LOCKED - _totalShares;
_totalShares += newLockedShares;
newShares -= newLockedShares;
}
} else {
unchecked {
_totalShares += newShares;
}
newShares = 0;
}
}
} else {
if (totalReceived < totalUnderlyingAtIndex) {
unchecked {
newShares = _getProportion128(totalReceived, _totalShares, totalUnderlyingAtIndex - totalReceived);
}
} else {
newShares = _totalShares;
}
}
// add new deposit shares
totalShares = _totalShares + newShares;
redeems[globalIndex] = Redeem(newShares, totalWithdrawn);
emit VaultRedeem(globalIndex);
}
// =========== USER REDEEM ============ //
/**
* @notice Redeem user deposit shares and withdrawn amount
*
* @dev
* Check if vault has already claimed shares for itself
*/
function _redeemUser() internal {
LastIndexInteracted memory _lastIndexInteracted = lastIndexInteracted;
LastIndexInteracted memory userIndexInteracted = userLastInteractions[msg.sender];
// check if strategy for index has already been redeemed
if (userIndexInteracted.index1 > 0 &&
(_lastIndexInteracted.index1 == 0 || userIndexInteracted.index1 < _lastIndexInteracted.index1)) {
// redeem interacted index 1
_redeemUserAction(userIndexInteracted.index1, true);
userIndexInteracted.index1 = 0;
if (userIndexInteracted.index2 > 0) {
if (_lastIndexInteracted.index2 == 0 || userIndexInteracted.index2 < _lastIndexInteracted.index1) {
// redeem interacted index 2
_redeemUserAction(userIndexInteracted.index2, false);
} else {
userIndexInteracted.index1 = userIndexInteracted.index2;
}
userIndexInteracted.index2 = 0;
}
userLastInteractions[msg.sender] = userIndexInteracted;
}
}
/**
* @notice Redeem user action for the `index`
* @param index index aw which user performed the action
* @param isFirstIndex Is this the first user index
*/
function _redeemUserAction(uint256 index, bool isFirstIndex) private {
User storage user = users[msg.sender];
IndexAction storage userIndex = userIndexAction[msg.sender][index];
// redeem user withdrawn amount at index
uint128 userWithdrawalShares = userIndex.withdrawShares;
if (userWithdrawalShares > 0) {
// calculate user withdrawn amount
uint128 userWithdrawnAmount = _getProportion128(redeems[index].withdrawnAmount, userWithdrawalShares, vaultIndexAction[index].withdrawShares);
user.owed += userWithdrawnAmount;
// calculate proportionate deposit to pay for performance fees on claim
uint128 proportionateDeposit;
uint128 sharesAtWithdrawal = user.shares + userWithdrawalShares;
if (isFirstIndex) {
// if user has 2 withdraws pending sum shares from the pending one as well
sharesAtWithdrawal += userIndexAction[msg.sender][index + 1].withdrawShares;
}
// check if withdrawal of all user shares was performes (all shares at the index of the action)
if (sharesAtWithdrawal > userWithdrawalShares) {
uint128 userTotalDeposit = user.activeDeposit;
proportionateDeposit = _getProportion128(userTotalDeposit, userWithdrawalShares, sharesAtWithdrawal);
user.activeDeposit = userTotalDeposit - proportionateDeposit;
} else {
proportionateDeposit = user.activeDeposit;
user.activeDeposit = 0;
}
user.withdrawnDeposits += proportionateDeposit;
// set user withdraw shares for index to 0
userIndex.withdrawShares = 0;
}
// redeem user deposit shares at index
uint128 userDepositAmount = userIndex.depositAmount;
if (userDepositAmount > 0) {
// calculate new user deposit shares
uint128 newUserShares = _getProportion128(userDepositAmount, redeems[index].depositShares, vaultIndexAction[index].depositAmount);
user.shares += newUserShares;
user.activeDeposit += userDepositAmount;
// set user deposit amount for index to 0
userIndex.depositAmount = 0;
}
emit UserRedeem(msg.sender, index);
}
// =========== INDEX FUNCTIONS ============ //
/**
* @dev Saves vault last interacted global index
* @param globalIndex Global index
*/
function _updateInteractedIndex(uint24 globalIndex) internal {
_updateLastIndexInteracted(lastIndexInteracted, globalIndex);
}
/**
* @dev Saves last user interacted global index
* @param globalIndex Global index
*/
function _updateUserInteractedIndex(uint24 globalIndex) internal {
_updateLastIndexInteracted(userLastInteractions[msg.sender], globalIndex);
}
/**
* @dev Update last index with which the system interacted
* @param lit Last interacted idex of a user or a vault
* @param globalIndex Global index
*/
function _updateLastIndexInteracted(LastIndexInteracted storage lit, uint24 globalIndex) private {
if (lit.index1 > 0) {
if (lit.index1 < globalIndex) {
lit.index2 = globalIndex;
}
} else {
lit.index1 = globalIndex;
}
}
/**
* @dev Gets current active global index from spool
*/
function _getActiveGlobalIndex() internal view returns(uint24) {
return spool.getActiveGlobalIndex();
}
/* ========== PRIVATE FUNCTIONS ========== */
/**
* @dev Ensures the vault is not currently reallocating
*/
function _noReallocation() private view {
require(!_isVaultReallocating(), "NRED");
}
/* ========== MODIFIERS ========== */
/**
* @dev Redeem given array of vault strategies
*/
modifier redeemVaultStrategiesModifier(address[] memory vaultStrategies) {
_redeemVaultStrategies(vaultStrategies);
_;
}
/**
* @dev Redeem user
*/
modifier redeemUserModifier() {
_redeemUser();
_;
}
/**
* @dev Ensures the vault is not currently reallocating
*/
modifier noReallocation() {
_noReallocation();
_;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;
import "../interfaces/vault/IVaultRestricted.sol";
import "./VaultIndexActions.sol";
/**
* @notice Implementation of the {IVaultRestricted} interface.
*
* @dev
* VaultRestricted extends VaultIndexActions and exposes functions restricted for Spool specific contracts.
*
* Index functions are executed when state changes are performed, to synchronize to vault with central Spool contract
*
* Functions:
* - payFees, called by fast withdraw, when user decides to fast withdraw its shares
* - reallocate, called by spool, sets new strategy allocation values and calculates what
* strategies to withdraw from and deposit to, to achieve the desired allocation
*/
abstract contract VaultRestricted is IVaultRestricted, VaultIndexActions {
using Bitwise for uint256;
// =========== FAST WITHDRAW FEES ============ //
/**
* @notice Notifies fee handler of user realized profits to calculate and store the fee.
* @dev
* Called by fast withdraw contract.
* Fee handler updates the fee storage slots and returns calculated fee value
* Fast withdraw transfers the calculated fee to the fee handler after.
*
* Requirements:
*
* - Caller must be the fast withdraw contract
*
* @param profit Total profit made by the user
* @return Fee amount calculated from the profit
*/
function payFees(uint256 profit) external override onlyFastWithdraw returns (uint256) {
return _payFees(profit);
}
/* ========== SPOOL REALLOCATE ========== */
/**
* @notice Update vault strategy proportions and reallocate funds according to the new proportions.
*
* @dev
* Requirements:
*
* - the caller must be the Spool contract
* - reallocation must not be in progress
* - new vault proportions must add up to `FULL_PERCENT`
*
* @param vaultStrategies Vault strategy addresses
* @param newVaultProportions New vault proportions
* @param finishedIndex Completed global index
* @param activeIndex current active global index, that we're setting reallocate for
*
* @return withdrawProportionsArray array of shares to be withdrawn from each vault strategy, and be later deposited back to other vault strategies
* @return newDepositProportions proportions to be deposited to strategies from all withdrawn funds (written in a uint word, 14bits each) values add up to `FULL_PERCENT`
*
*/
function reallocate(
address[] memory vaultStrategies,
uint256 newVaultProportions,
uint256 finishedIndex,
uint24 activeIndex
)
external
override
onlySpool
verifyStrategies(vaultStrategies)
redeemVaultStrategiesModifier(vaultStrategies)
noReallocation
returns(uint256[] memory withdrawProportionsArray, uint256 newDepositProportions)
{
(withdrawProportionsArray, newDepositProportions) = _adjustAllocation(vaultStrategies, newVaultProportions, finishedIndex);
proportions = newVaultProportions;
reallocationIndex = activeIndex;
_updateInteractedIndex(activeIndex);
emit Reallocate(reallocationIndex, newVaultProportions);
}
/**
* @notice Set new vault strategy allocation and calculate how the funds should be spread
*
* @dev
* Requirements:
*
* - new proportions must add up to 100% (`FULL_PERCENT`)
* - vault must withdraw from at least one strategy
* - vault must deposit to at least one strategy
* - vault total underlying must be more than zero
*
* @param vaultStrategies Vault strategy addresses
* @param newVaultProportions New vault proportions
* @param finishedIndex Completed global index
*
* @return withdrawProportionsArray array of shares to be withdrawn from each vault strategy, and be later deposited back to other vault strategies
* @return newDepositProportions proportions to be deposited to strategies from all withdrawn funds (written in a uint word, 14bits each) values add up to `FULL_PERCENT`
*/
function _adjustAllocation(
address[] memory vaultStrategies,
uint256 newVaultProportions,
uint256 finishedIndex
)
private returns(uint256[] memory, uint256)
{
uint256[] memory depositProportionsArray = new uint256[](vaultStrategies.length);
uint256[] memory withdrawProportionsArray = new uint256[](vaultStrategies.length);
(uint256[] memory stratUnderlyings, uint256 vaultTotalUnderlying) = _getStratsAndVaultUnderlying(vaultStrategies, finishedIndex);
require(vaultTotalUnderlying > 0, "NUL");
uint256 totalProportion;
uint256 totalDepositProportion;
uint256 lastDepositIndex;
{
// flags to check if reallocation will withdraw and reposit
bool didWithdraw = false;
bool willDeposit = false;
for (uint256 i; i < vaultStrategies.length; i++) {
uint256 newStratProportion = Bitwise.get14BitUintByIndex(newVaultProportions, i);
totalProportion += newStratProportion;
uint256 stratProportion;
if (stratUnderlyings[i] > 0) {
stratProportion = (stratUnderlyings[i] * FULL_PERCENT) / vaultTotalUnderlying;
}
// if current proportion is more than new - withdraw
if (stratProportion > newStratProportion) {
uint256 withdrawalProportion = stratProportion - newStratProportion;
if (withdrawalProportion < 10) // NOTE: skip if diff is less than 0.1%
continue;
uint256 withdrawalShareProportion = (withdrawalProportion * ACCURACY) / stratProportion;
withdrawProportionsArray[i] = withdrawalShareProportion;
didWithdraw = true;
} else if (stratProportion < newStratProportion) {
// if less - prepare for deposit
uint256 depositProportion = newStratProportion - stratProportion;
if (depositProportion < 10) // NOTE: skip if diff is less than 0.1%
continue;
depositProportionsArray[i] = depositProportion;
totalDepositProportion += depositProportion;
lastDepositIndex = i;
willDeposit = true;
}
}
// check if sum of new propotions equals to full percent
require(
totalProportion == FULL_PERCENT,
"BPP"
);
// Check if withdraw happened and if deposit will, otherwise revert
require(didWithdraw && willDeposit, "NRD");
}
// normalize deposit proportions to FULL_PERCENT
uint256 newDepositProportions;
uint256 totalDepositProp;
for (uint256 i; i <= lastDepositIndex; i++) {
if (depositProportionsArray[i] > 0) {
uint256 proportion = (depositProportionsArray[i] * FULL_PERCENT) / totalDepositProportion;
newDepositProportions = newDepositProportions.set14BitUintByIndex(i, proportion);
totalDepositProp += proportion;
}
}
newDepositProportions = newDepositProportions.set14BitUintByIndex(lastDepositIndex, FULL_PERCENT - totalDepositProp);
// store reallocation deposit proportions
depositProportions = newDepositProportions;
return (withdrawProportionsArray, newDepositProportions);
}
/**
* @notice Get strategies and vault underlying
* @param vaultStrategies Array of vault strategy addresses
* @param index Get the underlying amounts at index
*/
function _getStratsAndVaultUnderlying(address[] memory vaultStrategies, uint256 index)
private
view
returns (uint256[] memory, uint256)
{
uint256[] memory stratUnderlyings = new uint256[](vaultStrategies.length);
uint256 vaultTotalUnderlying;
for (uint256 i; i < vaultStrategies.length; i++) {
uint256 stratUnderlying = spool.getVaultTotalUnderlyingAtIndex(vaultStrategies[i], index);
stratUnderlyings[i] = stratUnderlying;
vaultTotalUnderlying += stratUnderlying;
}
return (stratUnderlyings, vaultTotalUnderlying);
}
}