diff --git a/src/interfaces/dependencies/vault/IDelegationManager.sol b/src/interfaces/dependencies/vault/IDelegationManager.sol new file mode 100644 index 0000000..eea076a --- /dev/null +++ b/src/interfaces/dependencies/vault/IDelegationManager.sol @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: LGPL-3.0 +pragma solidity ^0.8.19; + +import './IStrategy.sol'; +import './IStrategyManager.sol'; + +/** + * @title DelegationManager + * @notice This is the contract for delegation in Pell. The main functionalities of this contract are + * - enabling anyone to register as an operator in Pell + * - allowing operators to specify parameters related to stakers who delegate to them + * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time) + * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager) + */ + +struct QueuedWithdrawalParams { + // Array of strategies that the QueuedWithdrawal contains + IStrategy[] strategies; + // Array containing the amount of shares in each Strategy in the `strategies` array + uint256[] shares; + // The address of the withdrawer + address withdrawer; +} + +interface IDelegationManager { + // @notice Struct used for storing information about a single operator who has registered with Pell + struct OperatorDetails { + // @notice address to receive the rewards that the operator earns via serving applications built on Pell. + address earningsReceiver; + /** + * @notice Address to verify signatures when a staker wishes to delegate to the operator, as well as controlling "forced undelegations". + * @dev Signature verification follows these rules: + * 1) If this address is left as address(0), then any staker will be free to delegate to the operator, i.e. no signature verification will be performed. + * 2) If this address is an EOA (i.e. it has no code), then we follow standard ECDSA signature verification for delegations to the operator. + * 3) If this address is a contract (i.e. it has code) then we forward a call to the contract and verify that it returns the correct EIP-1271 "magic value". + */ + address delegationApprover; + /** + * @notice A minimum delay -- enforced between: + * 1) the operator signalling their intent to register for a service, via calling `Slasher.optIntoSlashing` + * and + * 2) the operator completing registration for the service, via the service ultimately calling `Slasher.recordFirstStakeUpdate` + * @dev note that for a specific operator, this value *cannot decrease*, i.e. if the operator wishes to modify their OperatorDetails, + * then they are only allowed to either increase this value or keep it the same. + */ + uint32 stakerOptOutWindow; + } + + /** + * @notice Abstract struct used in calculating an EIP712 signature for a staker to approve that they (the staker themselves) delegate to a specific operator. + * @dev Used in computing the `STAKER_DELEGATION_TYPEHASH` and as a reference in the computation of the stakerDigestHash in the `delegateToBySignature` function. + */ + struct StakerDelegation { + // the staker who is delegating + address staker; + // the operator being delegated to + address operator; + // the staker's nonce + uint256 nonce; + // the expiration timestamp (UTC) of the signature + uint256 expiry; + } + + /** + * @notice Abstract struct used in calculating an EIP712 signature for an operator's delegationApprover to approve that a specific staker delegate to the operator. + * @dev Used in computing the `DELEGATION_APPROVAL_TYPEHASH` and as a reference in the computation of the approverDigestHash in the `_delegate` function. + */ + struct DelegationApproval { + // the staker who is delegating + address staker; + // the operator being delegated to + address operator; + // the operator's provided salt + bytes32 salt; + // the expiration timestamp (UTC) of the signature + uint256 expiry; + } + + /** + * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored. + * In functions that operate on existing queued withdrawals -- e.g. completeQueuedWithdrawal`, the data is resubmitted and the hash of the submitted + * data is computed by `calculateWithdrawalRoot` and checked against the stored hash in order to confirm the integrity of the submitted data. + */ + struct Withdrawal { + // The address that originated the Withdrawal + address staker; + // The address that the staker was delegated to at the time that the Withdrawal was created + address delegatedTo; + // The address that can complete the Withdrawal + will receive funds when completing the withdrawal + address withdrawer; + // Nonce used to guarantee that otherwise identical withdrawals have unique hashes + uint256 nonce; + // Block timestamp when the Withdrawal was created + uint32 startTimestamp; + // Array of strategies that the Withdrawal contains + IStrategy[] strategies; + // Array containing the amount of shares in each Strategy in the `strategies` array + uint256[] shares; + } + + // @notice Emitted when a new operator registers in Pell and provides their OperatorDetails. + event OperatorRegistered(address indexed operator, OperatorDetails operatorDetails); + + /// @notice Emitted when an operator updates their OperatorDetails to @param newOperatorDetails + event OperatorDetailsModified(address indexed operator, OperatorDetails newOperatorDetails); + + /** + * @notice Emitted when @param operator indicates that they are updating their MetadataURI string + * @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain indexing + */ + event OperatorMetadataURIUpdated(address indexed operator, string metadataURI); + + /// @notice Emitted whenever an operator's shares are increased for a given strategy. Note that shares is the delta in the operator's shares. + event OperatorSharesIncreased(address indexed operator, address staker, IStrategy strategy, uint256 shares); + + /// @notice Emitted whenever an operator's shares are decreased for a given strategy. Note that shares is the delta in the operator's shares. + event OperatorSharesDecreased(address indexed operator, address staker, IStrategy strategy, uint256 shares); + + /// @notice Emitted when @param staker delegates to @param operator. + event StakerDelegated(address indexed staker, address indexed operator); + + /// @notice Emitted when @param staker undelegates from @param operator. + event StakerUndelegated(address indexed staker, address indexed operator); + + /// @notice Emitted when @param staker is undelegated via a call not originating from the staker themself + event StakerForceUndelegated(address indexed staker, address indexed operator); + + /** + * @notice Emitted when a new withdrawal is queued. + * @param withdrawalRoot Is the hash of the `withdrawal`. + * @param withdrawal Is the withdrawal itself. + */ + event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal); + + /// @notice Emitted when a queued withdrawal is completed + event WithdrawalCompleted(bytes32 withdrawalRoot); + + /// @notice Emitted when the `minWithdrawalDelay` variable is modified from `previousValue` to `newValue`. + event MinWithdrawalDelaySet(uint256 previousValue, uint256 newValue); + + /// @notice Emitted when the `strategyWithdrawalDelay` variable is modified from `previousValue` to `newValue`. + event StrategyWithdrawalDelaySet(IStrategy strategy, uint256 previousValue, uint256 newValue); + + event UpdateWrappedTokenGateway(address previousGateway, address currentGateway); + + /** + * @notice Registers the caller as an operator in Pell. + * @param registeringOperatorDetails is the `OperatorDetails` for the operator. + * @param metadataURI is a URI for the operator's metadata, i.e. a link providing more details on the operator. + * + * @dev Once an operator is registered, they cannot 'deregister' as an operator, and they will forever be considered "delegated to themself". + * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0). + * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event + */ + function registerAsOperator(OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI) external; + + /** + * @notice Updates an operator's stored `OperatorDetails`. + * @param newOperatorDetails is the updated `OperatorDetails` for the operator, to replace their current OperatorDetails`. + * + * @dev The caller must have previously registered as an operator in Pell. + * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0). + */ + function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external; + + /** + * @notice Called by an operator to emit an `OperatorMetadataURIUpdated` event indicating the information has updated. + * @param metadataURI The URI for metadata associated with an operator + * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event + */ + function updateOperatorMetadataURI(string calldata metadataURI) external; + + /** + * @notice Undelegates the staker from the operator who they are delegated to. Puts the staker into the "undelegation limbo" mode of DelegationManager + * and queues a withdrawal of all of the staker's shares in the StrategyManager (to the staker), if necessary. + * @param staker The account to be undelegated. + * @return withdrawalRoot The root of the newly queued withdrawal, if a withdrawal was queued. Otherwise just bytes32(0). + * + * @dev Reverts if the `staker` is also an operator, since operators are not allowed to undelegate from themselves. + * @dev Reverts if the caller is not the staker, nor the operator who the staker is delegated to, nor the operator's specified "delegationApprover" + * @dev Reverts if the `staker` is already undelegated. + */ + function undelegate(address staker) external returns (bytes32[] memory withdrawalRoot); + + /** + * Allows a staker to withdraw some shares. Withdrawn shares/strategies are immediately removed + * from the staker. If the staker is delegated, withdrawn shares/strategies are also removed from + * their operator. + * + * All withdrawn shares/strategies are placed in a queue and can be fully withdrawn after a delay. + */ + function queueWithdrawals(QueuedWithdrawalParams[] calldata queuedWithdrawalParams) external returns (bytes32[] memory); + + /** + * @notice Used to complete the specified `withdrawal`. The caller must match `withdrawal.withdrawer` + * @param withdrawal The Withdrawal to complete. + * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `withdrawal.strategies` array. + * This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused) + * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array + * @param receiveAsTokens If true, the shares specified in the withdrawal will be withdrawn from the specified strategies themselves + * and sent to the caller, through calls to `withdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies + * will simply be transferred to the caller directly. + * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw` + * @dev beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` and `withdrawal.withdrawer != withdrawal.staker`, note that + * any beaconChainETHStrategy shares in the `withdrawal` will be _returned to the staker_, rather than transferred to the withdrawer, unlike shares in + * any other strategies, which will be transferred to the withdrawer. + */ + function completeQueuedWithdrawal( + Withdrawal calldata withdrawal, + IERC20[] calldata tokens, + uint256 middlewareTimesIndex, + bool receiveAsTokens + ) external; + + /** + * @notice Array-ified version of `completeQueuedWithdrawal`. + * Used to complete the specified `withdrawals`. The function caller must match `withdrawals[...].withdrawer` + * @param withdrawals The Withdrawals to complete. + * @param tokens Array of tokens for each Withdrawal. See `completeQueuedWithdrawal` for the usage of a single array. + * @param middlewareTimesIndexes One index to reference per Withdrawal. See `completeQueuedWithdrawal` for the usage of a single index. + * @param receiveAsTokens Whether or not to complete each withdrawal as tokens. See `completeQueuedWithdrawal` for the usage of a single boolean. + * @dev See `completeQueuedWithdrawal` for relevant dev tags + */ + function completeQueuedWithdrawals( + Withdrawal[] calldata withdrawals, + IERC20[][] calldata tokens, + uint256[] calldata middlewareTimesIndexes, + bool[] calldata receiveAsTokens + ) external; + + /** + * @notice Increases a staker's delegated share balance in a strategy. + * @param staker The address to increase the delegated shares for their operator. + * @param strategy The strategy in which to increase the delegated shares. + * @param shares The number of shares to increase. + * + * @dev *If the staker is actively delegated*, then increases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing. + * @dev Callable only by the StrategyManager. + */ + function increaseDelegatedShares(address staker, IStrategy strategy, uint256 shares) external; + + /** + * @notice Decreases a staker's delegated share balance in a strategy. + * @param staker The address to increase the delegated shares for their operator. + * @param strategy The strategy in which to decrease the delegated shares. + * @param shares The number of shares to decrease. + * + * @dev *If the staker is actively delegated*, then decreases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing. + * @dev Callable only by the StrategyManager. + */ + function decreaseDelegatedShares(address staker, IStrategy strategy, uint256 shares) external; + + /** + * @notice returns the address of the operator that `staker` is delegated to. + * @notice Mapping: staker => operator whom the staker is currently delegated to. + * @dev Note that returning address(0) indicates that the staker is not actively delegated to any operator. + */ + function delegatedTo(address staker) external view returns (address); + + /** + * @notice Returns the OperatorDetails struct associated with an `operator`. + */ + function operatorDetails(address operator) external view returns (OperatorDetails memory); + + /* + * @notice Returns the earnings receiver address for an operator + */ + function earningsReceiver(address operator) external view returns (address); + + /** + * @notice Returns the delegationApprover account for an operator + */ + function delegationApprover(address operator) external view returns (address); + + /** + * @notice Returns the stakerOptOutWindow for an operator + */ + function stakerOptOutWindow(address operator) external view returns (uint256); + + /** + * @notice Given array of strategies, returns array of shares for the operator + */ + function getOperatorShares(address operator, IStrategy[] memory strategies) external view returns (uint256[] memory); + + /** + * @notice Given a list of strategies, return the minimum cooldown that must pass to withdraw + * from all the inputted strategies. Return value is >= minWithdrawalDelay as this is the global min withdrawal delay. + * @param strategies The strategies to check withdrawal delays for + */ + function getWithdrawalDelay(IStrategy[] calldata strategies) external view returns (uint256); + + /** + * @notice returns the total number of shares in `strategy` that are delegated to `operator`. + * @notice Mapping: operator => strategy => total number of shares in the strategy delegated to the operator. + * @dev By design, the following invariant should hold for each Strategy: + * (operator's shares in delegation manager) = sum (shares above zero of all stakers delegated to operator) + * = sum (delegateable shares of all stakers delegated to the operator) + */ + function operatorShares(address operator, IStrategy strategy) external view returns (uint256); + + /** + * @notice Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise. + */ + function isDelegated(address staker) external view returns (bool); + + /** + * @notice Returns true is an operator has previously registered for delegation. + */ + function isOperator(address operator) external view returns (bool); + + /// @notice Mapping: staker => number of signed delegation nonces (used in `delegateToBySignature`) from the staker that the contract has already checked + function stakerNonce(address staker) external view returns (uint256); + + /** + * @notice Mapping: delegationApprover => 32-byte salt => whether or not the salt has already been used by the delegationApprover. + * @dev Salts are used in the `delegateTo` and `delegateToBySignature` functions. Note that these functions only process the delegationApprover's + * signature + the provided salt if the operator being delegated to has specified a nonzero address as their `delegationApprover`. + */ + function delegationApproverSaltIsSpent(address _delegationApprover, bytes32 salt) external view returns (bool); + + /** + * @notice Minimum delay enforced by this contract for completing queued withdrawals. Cooldown, and adjustable by this contract's owner, + * up to a maximum of `MAX_WITHDRAWAL_DELAY`. Minimum value is 0 (i.e. no delay enforced). + * Note that strategies each have a separate withdrawal delay, which can be greater than this value. So the minimum cooldown that must pass + * to withdraw a strategy is MAX(minWithdrawalDelay, strategyWithdrawalDelay[strategy]) + */ + function minWithdrawalDelay() external view returns (uint256); + + /** + * @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. Cooldown, and adjustable by this contract's owner, + * up to a maximum of `MAX_WITHDRAWAL_DELAY`. Minimum value is 0 (i.e. no delay enforced). + */ + function strategyWithdrawalDelay(IStrategy strategy) external view returns (uint256); + + /** + * @notice Calculates the digestHash for a `staker` to sign to delegate to an `operator` + * @param staker The signing staker + * @param operator The operator who is being delegated to + * @param expiry The desired expiry time of the staker's signature + */ + function calculateCurrentStakerDelegationDigestHash(address staker, address operator, uint256 expiry) external view returns (bytes32); + + /** + * @notice Calculates the digest hash to be signed and used in the `delegateToBySignature` function + * @param staker The signing staker + * @param _stakerNonce The nonce of the staker. In practice we use the staker's current nonce, stored at `stakerNonce[staker]` + * @param operator The operator who is being delegated to + * @param expiry The desired expiry time of the staker's signature + */ + function calculateStakerDelegationDigestHash( + address staker, + uint256 _stakerNonce, + address operator, + uint256 expiry + ) external view returns (bytes32); + + /** + * @notice Calculates the digest hash to be signed by the operator's delegationApprove and used in the `delegateTo` and `delegateToBySignature` functions. + * @param staker The account delegating their stake + * @param operator The account receiving delegated stake + * @param _delegationApprover the operator's `delegationApprover` who will be signing the delegationHash (in general) + * @param approverSalt A unique and single use value associated with the approver signature. + * @param expiry Time after which the approver's signature becomes invalid + */ + function calculateDelegationApprovalDigestHash( + address staker, + address operator, + address _delegationApprover, + bytes32 approverSalt, + uint256 expiry + ) external view returns (bytes32); + + /// @notice The EIP-712 typehash for the contract's domain + function DOMAIN_TYPEHASH() external view returns (bytes32); + + /// @notice The EIP-712 typehash for the StakerDelegation struct used by the contract + function STAKER_DELEGATION_TYPEHASH() external view returns (bytes32); + + /// @notice The EIP-712 typehash for the DelegationApproval struct used by the contract + function DELEGATION_APPROVAL_TYPEHASH() external view returns (bytes32); + + /** + * @notice Getter function for the current EIP-712 domain separator for this contract. + * + * @dev The domain separator will change in the event of a fork that changes the ChainID. + * @dev By introducing a domain separator the DApp developers are guaranteed that there can be no signature collision. + * for more detailed information please read EIP-712. + */ + function domainSeparator() external view returns (bytes32); + + /// @notice Mapping: staker => cumulative number of queued withdrawals they have ever initiated. + /// @dev This only increments (doesn't decrement), and is used to help ensure that otherwise identical withdrawals have unique hashes. + function cumulativeWithdrawalsQueued(address staker) external view returns (uint256); + + /// @notice Returns the keccak256 hash of `withdrawal`. + function calculateWithdrawalRoot(Withdrawal memory withdrawal) external pure returns (bytes32); +} diff --git a/src/interfaces/dependencies/vault/ILendingPool.sol b/src/interfaces/dependencies/vault/ILendingPool.sol index 284e233..5f80bfc 100644 --- a/src/interfaces/dependencies/vault/ILendingPool.sol +++ b/src/interfaces/dependencies/vault/ILendingPool.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; +import {DataTypes} from '../../../dependencies/vault/DataTypes.sol'; + interface ILendingPool { /** * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. @@ -29,4 +31,12 @@ interface ILendingPool { * */ function withdraw(address asset, uint256 amount, address to) external returns (uint256); + + /** + * @notice Returns the state and configuration of the reserve + * @param asset The address of the underlying asset of the reserve + * @return The state and configuration data of the reserve + */ + function getReserveData(address asset) external view returns (DataTypes.ReserveData memory); + } diff --git a/src/interfaces/dependencies/vault/ISlasher.sol b/src/interfaces/dependencies/vault/ISlasher.sol new file mode 100644 index 0000000..8c23670 --- /dev/null +++ b/src/interfaces/dependencies/vault/ISlasher.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: LGPL-3.0 +pragma solidity ^0.8.19; + +import './IStrategyManager.sol'; + +/** + * @title Interface for the primary 'slashing' contract for Pell. + * @notice See the `Slasher` contract itself for implementation details. + */ +interface ISlasher { + // struct used to store information about the current state of an operator's obligations to middlewares they are serving + struct MiddlewareTimes { + // The update timestamp for the middleware whose most recent update was earliest, i.e. the 'stalest' update out of all middlewares the operator is serving + uint32 stalestUpdateTimestamp; + // The latest 'serveUntilTimestamp' from all of the middleware that the operator is serving + uint32 latestServeUntilTimestamp; + } + + // struct used to store details relevant to a single middleware that an operator has opted-in to serving + struct MiddlewareDetails { + // the timestamp at which the contract begins being able to finalize the operator's registration with the service via calling `recordFirstStakeUpdate` + uint32 registrationMayBeginAtTimestamp; + // the timestamp before which the contract is allowed to slash the user + uint32 contractCanSlashOperatorUntilTimestamp; + // the timestamp at which the middleware's view of the operator's stake was most recently updated + uint32 latestUpdateTimestamp; + } + + /// @notice Emitted when a middleware times is added to `operator`'s array. + event MiddlewareTimesAdded(address operator, uint256 index, uint32 stalestUpdateTimestamp, uint32 latestServeUntilTimestamp); + + /// @notice Emitted when `operator` begins to allow `contractAddress` to slash them. + event OptedIntoSlashing(address indexed operator, address indexed contractAddress); + + /// @notice Emitted when `contractAddress` signals that it will no longer be able to slash `operator` after the `contractCanSlashOperatorUntilTimestamp`. + event SlashingAbilityRevoked(address indexed operator, address indexed contractAddress, uint32 contractCanSlashOperatorUntilTimestamp); + + /** + * @notice Emitted when `slashingContract` 'freezes' the `slashedOperator`. + * @dev The `slashingContract` must have permission to slash the `slashedOperator`, i.e. `canSlash(slasherOperator, slashingContract)` must return 'true'. + */ + event OperatorFrozen(address indexed slashedOperator, address indexed slashingContract); + + /// @notice Emitted when `previouslySlashedAddress` is 'unfrozen', allowing them to again move deposited funds within Pell. + event FrozenStatusReset(address indexed previouslySlashedAddress); + + /** + * @notice Gives the `contractAddress` permission to slash the funds of the caller. + * @dev Typically, this function must be called prior to registering for a middleware. + */ + function optIntoSlashing(address contractAddress) external; + + /** + * @notice Used for 'slashing' a certain operator. + * @param toBeFrozen The operator to be frozen. + * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop. + * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`. + */ + function freezeOperator(address toBeFrozen) external; + + /** + * @notice Removes the 'frozen' status from each of the `frozenAddresses` + * @dev Callable only by the contract owner (i.e. governance). + */ + function resetFrozenStatus(address[] calldata frozenAddresses) external; + + /** + * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration + * is slashable until serveUntil + * @param operator the operator whose stake update is being recorded + * @param serveUntilTimestamp the timestamp until which the operator's stake at the current timestamp is slashable + * @dev adds the middleware's slashing contract to the operator's linked list + */ + function recordFirstStakeUpdate(address operator, uint32 serveUntilTimestamp) external; + + /** + * @notice this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals) + * to make sure the operator's stake at updateTimestamp is slashable until serveUntil + * @param operator the operator whose stake update is being recorded + * @param updateTimestamp the timestamp for which the stake update is being recorded + * @param serveUntilTimestamp the timestamp until which the operator's stake at updateTimestamp is slashable + * @param insertAfter the element of the operators linked list that the currently updating middleware should be inserted after + * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions, + * but it is anticipated to be rare and not detrimental. + */ + function recordStakeUpdate(address operator, uint32 updateTimestamp, uint32 serveUntilTimestamp, uint256 insertAfter) external; + + /** + * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration + * is slashable until serveUntil + * @param operator the operator whose stake update is being recorded + * @param serveUntilTimestamp the timestamp until which the operator's stake at the current timestamp is slashable + * @dev removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to + * slash `operator` once `serveUntil` is reached + */ + function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilTimestamp) external; + + /// @notice The StrategyManager contract of Pell + function strategyManager() external view returns (IStrategyManager); + + /** + * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to + * slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed + * and the staker's status is reset (to 'unfrozen'). + * @param staker The staker of interest. + * @return Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated + * to an operator who has their status set to frozen. Otherwise returns 'false'. + */ + function isFrozen(address staker) external view returns (bool); + + /// @notice Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`. + function canSlash(address toBeSlashed, address slashingContract) external view returns (bool); + + /// @notice Returns the timestamp until which `serviceContract` is allowed to slash the `operator`. + function contractCanSlashOperatorUntilTimestamp(address operator, address serviceContract) external view returns (uint32); + + /// @notice Returns the timestamp at which the `serviceContract` last updated its view of the `operator`'s stake + function latestUpdateTimestamp(address operator, address serviceContract) external view returns (uint32); + + /// @notice A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`. + function getCorrectValueForInsertAfter(address operator, uint32 updateTimestamp) external view returns (uint256); + + /** + * @notice Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartTimestamp`, with `middlewareTimesIndex` used + * to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `operatorToMiddlewareTimes[operator]`). The specified + * struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal. + * This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartTimestamp`, *or* in the event + * that an incorrect `middlewareTimesIndex` is supplied, even if one or more correct inputs exist. + * @param operator Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator, + * this address is the operator *who the staker was delegated to* at the time of the `withdrawalStartTimestamp`. + * @param withdrawalStartTimestamp The timestamp at which the withdrawal was initiated. + * @param middlewareTimesIndex Indicates an index in `operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw + * @dev The correct `middlewareTimesIndex` input should be computable off-chain. + */ + function canWithdraw(address operator, uint32 withdrawalStartTimestamp, uint256 middlewareTimesIndex) external returns (bool); + + /** + * operator => + * [ + * ( + * the least recent update timestamp of all of the middlewares it's serving/served, + * latest time that the stake bonded at that update needed to serve until + * ) + * ] + */ + function operatorToMiddlewareTimes(address operator, uint256 arrayIndex) external view returns (MiddlewareTimes memory); + + /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator].length` + function middlewareTimesLength(address operator) external view returns (uint256); + + /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].stalestUpdateTimestamp`. + function getMiddlewareTimesIndexStalestUpdateTimestamp(address operator, uint32 index) external view returns (uint32); + + /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntil`. + function getMiddlewareTimesIndexServeUntilTimestamp(address operator, uint32 index) external view returns (uint32); + + /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`. + function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256); + + /// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`). + function operatorWhitelistedContractsLinkedListEntry(address operator, address node) external view returns (bool, uint256, uint256); +} diff --git a/src/interfaces/dependencies/vault/IStrategy.sol b/src/interfaces/dependencies/vault/IStrategy.sol new file mode 100644 index 0000000..c614383 --- /dev/null +++ b/src/interfaces/dependencies/vault/IStrategy.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: LGPL-3.0 +pragma solidity ^0.8.19; + +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +/** + * @title Minimal interface for an `Strategy` contract. + * @notice Custom `Strategy` implementations may expand extensively on this interface. + */ +interface IStrategy { + /** + * @notice Used to deposit tokens into this Strategy + * @param token is the ERC20 token being deposited + * @param amount is the amount of token being deposited + * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's + * `depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well. + * @return newShares is the number of new shares issued at the current exchange ratio. + */ + function deposit(IERC20 token, uint256 amount) external returns (uint256); + + /** + * @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address + * @param recipient is the address to receive the withdrawn funds + * @param token is the ERC20 token being transferred out + * @param amountShares is the amount of shares being withdrawn + * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's + * other functions, and individual share balances are recorded in the strategyManager as well. + */ + function withdraw(address recipient, IERC20 token, uint256 amountShares) external; + + /** + * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy. + * @notice In contrast to `sharesToUnderlyingView`, this function **may** make state modifications + * @param amountShares is the amount of shares to calculate its conversion into the underlying token + * @return The amount of underlying tokens corresponding to the input `amountShares` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function sharesToUnderlying(uint256 amountShares) external returns (uint256); + + /** + * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy. + * @notice In contrast to `underlyingToSharesView`, this function **may** make state modifications + * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares + * @return The amount of underlying tokens corresponding to the input `amountShares` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function underlyingToShares(uint256 amountUnderlying) external returns (uint256); + + /** + * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in + * this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications + */ + function userUnderlying(address user) external returns (uint256); + + /** + * @notice convenience function for fetching the current total shares of `user` in this strategy, by + * querying the `strategyManager` contract + */ + function shares(address user) external view returns (uint256); + + /** + * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy. + * @notice In contrast to `sharesToUnderlying`, this function guarantees no state modifications + * @param amountShares is the amount of shares to calculate its conversion into the underlying token + * @return The amount of shares corresponding to the input `amountUnderlying` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function sharesToUnderlyingView(uint256 amountShares) external view returns (uint256); + + /** + * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy. + * @notice In contrast to `underlyingToShares`, this function guarantees no state modifications + * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares + * @return The amount of shares corresponding to the input `amountUnderlying` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function underlyingToSharesView(uint256 amountUnderlying) external view returns (uint256); + + /** + * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in + * this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications + */ + function userUnderlyingView(address user) external view returns (uint256); + + /// @notice The underlying token for shares in this Strategy + function underlyingToken() external view returns (IERC20); + + /// @notice The total number of extant shares in this Strategy + function totalShares() external view returns (uint256); + + /// @notice Returns either a brief string explaining the strategy's goal & purpose, or a link to metadata that explains in more detail. + function explanation() external view returns (string memory); +} diff --git a/src/interfaces/dependencies/vault/IStrategyManager.sol b/src/interfaces/dependencies/vault/IStrategyManager.sol new file mode 100644 index 0000000..2288243 --- /dev/null +++ b/src/interfaces/dependencies/vault/IStrategyManager.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: LGPL-3.0 +pragma solidity ^0.8.19; + +import './IStrategy.sol'; +import './ISlasher.sol'; + +/** + * @title Interface for the primary entrypoint for funds into Pell. + * @notice See the `StrategyManager` contract itself for implementation details. + */ +interface IStrategyManager { + /** + * @notice Emitted when a new deposit occurs on behalf of `staker`. + * @param staker Is the staker who is depositing funds into Pell. + * @param strategy Is the strategy that `staker` has deposited into. + * @param token Is the token that `staker` deposited. + * @param shares Is the number of new shares `staker` has been granted in `strategy`. + */ + event Deposit(address staker, IERC20 token, IStrategy strategy, uint256 shares); + + /// @notice Emitted when `thirdPartyTransfersForbidden` is updated for a strategy and value by the owner + event UpdatedThirdPartyTransfersForbidden(IStrategy strategy, bool value); + + /// @notice Emitted when the `strategyWhitelister` is changed + event StrategyWhitelisterChanged(address previousAddress, address newAddress); + + /// @notice Emitted when a strategy is added to the approved list of strategies for deposit + event StrategyAddedToDepositWhitelist(IStrategy strategy); + + /// @notice Emitted when a strategy is removed from the approved list of strategies for deposit + event StrategyRemovedFromDepositWhitelist(IStrategy strategy); + + /** + * @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender` + * @param strategy is the specified strategy where deposit is to be made, + * @param token is the denomination in which the deposit is to be made, + * @param amount is the amount of token to be deposited in the strategy by the staker + * @return shares The amount of new shares in the `strategy` created as part of the action. + * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf. + * @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen). + * + * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors + * where the token balance and corresponding strategy shares are not in sync upon reentrancy. + */ + function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount) external returns (uint256 shares); + + /** + * @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`, + * who must sign off on the action. + * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed + * purely to help one address deposit 'for' another. + * @param strategy is the specified strategy where deposit is to be made, + * @param token is the denomination in which the deposit is to be made, + * @param amount is the amount of token to be deposited in the strategy by the staker + * @param staker the staker that the deposited assets will be credited to + * @param expiry the timestamp at which the signature expires + * @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward + * following EIP-1271 if the `staker` is a contract + * @return shares The amount of new shares in the `strategy` created as part of the action. + * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf. + * @dev A signature is required for this function to eliminate the possibility of griefing attacks, specifically those + * targeting stakers who may be attempting to undelegate. + * @dev Cannot be called if thirdPartyTransfersForbidden is set to true for this strategy + * + * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors + * where the token balance and corresponding strategy shares are not in sync upon reentrancy + */ + function depositIntoStrategyWithSignature( + IStrategy strategy, + IERC20 token, + uint256 amount, + address staker, + uint256 expiry, + bytes memory signature + ) external returns (uint256 shares); + + /// @notice Used by the DelegationManager to remove a Staker's shares from a particular strategy when entering the withdrawal queue + function removeShares(address staker, IStrategy strategy, uint256 shares) external; + + /// @notice Used by the DelegationManager to award a Staker some shares that have passed through the withdrawal queue + function addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) external; + + /// @notice Used by the DelegationManager to convert withdrawn shares to tokens and send them to a recipient + function withdrawSharesAsTokens(address recipient, IStrategy strategy, uint256 shares, IERC20 token) external; + + /// @notice Returns the current shares of `user` in `strategy` + function stakerStrategyShares(address user, IStrategy strategy) external view returns (uint256 shares); + + /** + * @notice Get all details on the staker's deposits and corresponding shares + * @return (staker's strategies, shares in these strategies) + */ + function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory); + + /// @notice Simple getter function that returns `stakerStrategyList[staker].length`. + function stakerStrategyListLength(address staker) external view returns (uint256); + + /** + * @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into + * @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already) + * @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy + */ + function addStrategiesToDepositWhitelist( + IStrategy[] calldata strategiesToWhitelist, + bool[] calldata thirdPartyTransfersForbiddenValues + ) external; + + /** + * @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into + * @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it) + */ + function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external; + + /// @notice Returns the single, central Slasher contract of Pell + function slasher() external view returns (ISlasher); + + /// @notice Returns the address of the `strategyWhitelister` + function strategyWhitelister() external view returns (address); + + /** + * @notice Returns bool for whether or not `strategy` enables credit transfers. i.e enabling + * depositIntoStrategyWithSignature calls or queueing withdrawals to a different address than the staker. + */ + function thirdPartyTransfersForbidden(IStrategy strategy) external view returns (bool); +} diff --git a/src/vault/CDPVaultCore.sol b/src/vault/CDPVaultCore.sol index 9cfde4e..ba7a3f4 100644 --- a/src/vault/CDPVaultCore.sol +++ b/src/vault/CDPVaultCore.sol @@ -41,4 +41,6 @@ abstract contract CDPVaultCore is ICDPVault, SatoshiOwnable, UUPSUpgradeable { IERC20(token).transfer(to, amount); emit TokenTransferred(token, to, amount); } + + function getPosition() external view virtual returns (address, uint256); } diff --git a/src/vault/PellVault.sol b/src/vault/PellVault.sol new file mode 100644 index 0000000..95e9342 --- /dev/null +++ b/src/vault/PellVault.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ISatoshiCore} from "../interfaces/core/ISatoshiCore.sol"; +import {IStrategyManager} from "../interfaces/dependencies/vault/IStrategyManager.sol"; +import {IDelegationManager, QueuedWithdrawalParams} from "../interfaces/dependencies/vault/IDelegationManager.sol"; +import {IStrategy} from "../interfaces/dependencies/vault/IStrategy.sol"; +import {CDPVaultCore} from "./CDPVaultCore.sol"; + +contract PellVault is CDPVaultCore { + address public pellStrategy; + function initialize(bytes calldata data) external override initializer { + __UUPSUpgradeable_init_unchained(); + (ISatoshiCore _satoshiCore, address tokenAddress_, address vaultManager_, address pellStrategy_) = _decodeInitializeData(data); + __SatoshiOwnable_init(_satoshiCore); + TOKEN_ADDRESS = tokenAddress_; + vaultManager = vaultManager_; + pellStrategy = pellStrategy_; + } + + modifier onlyVaultManager() { + require(msg.sender == vaultManager, "Only vault manager"); + _; + } + + function executeStrategy(bytes calldata data) external override onlyVaultManager { + uint256 amount = _decodeExecuteData(data); + IERC20(TOKEN_ADDRESS).approve(strategyAddr, amount); + // deposit token to lending + IStrategyManager(strategyAddr).depositIntoStrategy(IStrategy(pellStrategy), IERC20(TOKEN_ADDRESS), amount); + } + + function exitStrategy(bytes calldata data) external override onlyVaultManager returns (uint256) { + uint256 amount = _decodeExitData(data); + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = IStrategy(pellStrategy); + + uint256[] memory shares = new uint256[](1); + shares[0] = amount; + + QueuedWithdrawalParams[] memory queuedWithdrawal = new QueuedWithdrawalParams[](1); + queuedWithdrawal[0] = QueuedWithdrawalParams({ + strategies: strategies, + shares: shares, + withdrawer: address(this) + }); + + // withdraw token from lending + IDelegationManager(0x230B442c0802fE83DAf3d2656aaDFD16ca1E1F66).queueWithdrawals(queuedWithdrawal); + + return amount; + } + + function executeCall(address dest, bytes calldata data) external onlyOwner { + (bool success, bytes memory res) = dest.call(data); + require(success, string(res)); + } + + function constructExecuteStrategyData(uint256 amount) external pure override returns (bytes memory) { + return abi.encode(amount); + } + + function constructExitStrategyData(uint256 amount) external pure override returns (bytes memory) { + return abi.encode(amount); + } + + function _decodeInitializeData(bytes calldata data) internal pure returns (ISatoshiCore, address, address, address) { + return abi.decode(data, (ISatoshiCore, address, address, address)); + } + + function _decodeExecuteData(bytes calldata data) internal pure returns (uint256 amount) { + return abi.decode(data, (uint256)); + } + + function _decodeExitData(bytes calldata data) internal pure returns (uint256 amount) { + return abi.decode(data, (uint256)); + } + + function getPosition() external view override returns (address, uint256) { + return (TOKEN_ADDRESS, IStrategy(pellStrategy).userUnderlyingView(address(this))); + } +} diff --git a/src/vault/VaultCore.sol b/src/vault/VaultCore.sol index 71aff18..046fe59 100644 --- a/src/vault/VaultCore.sol +++ b/src/vault/VaultCore.sol @@ -51,4 +51,6 @@ abstract contract VaultCore is INYMVault, SatoshiOwnable, UUPSUpgradeable { IERC20(token).transfer(to, amount); emit TokenTransferred(token, to, amount); } + + function getPosition() external view virtual returns (address, uint256); } diff --git a/src/vault/aaveVault.sol b/src/vault/aaveVault.sol index 889377c..d5811c0 100644 --- a/src/vault/aaveVault.sol +++ b/src/vault/aaveVault.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {DataTypes} from '../dependencies/vault/DataTypes.sol'; import {ISatoshiCore} from "../interfaces/core/ISatoshiCore.sol"; import {ILendingPool} from "../interfaces/dependencies/vault/ILendingPool.sol"; import {VaultCore} from "./VaultCore.sol"; @@ -48,4 +49,9 @@ contract AAVEVault is VaultCore { function _decodeExitData(bytes calldata data) internal pure returns (uint256 amount) { return abi.decode(data, (uint256)); } + + function getPosition() external view override returns (address, uint256) { + DataTypes.ReserveData memory data = ILendingPool(strategyAddr).getReserveData(STABLE_TOKEN_ADDRESS); + return (STABLE_TOKEN_ADDRESS, IERC20(data.aTokenAddress).balanceOf(address(this))); + } } diff --git a/src/vault/avalonVault.sol b/src/vault/avalonVault.sol index 661494a..ff86b77 100644 --- a/src/vault/avalonVault.sol +++ b/src/vault/avalonVault.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.19; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ISatoshiCore} from "../interfaces/core/ISatoshiCore.sol"; import {IPool} from "../interfaces/dependencies/vault/IPool.sol"; +import {DataTypes} from '../dependencies/vault/DataTypes.sol'; + import {CDPVaultCore} from "./CDPVaultCore.sol"; contract AvalonVault is CDPVaultCore { @@ -54,4 +56,9 @@ contract AvalonVault is CDPVaultCore { function _decodeExitData(bytes calldata data) internal pure returns (uint256 amount) { return abi.decode(data, (uint256)); } + + function getPosition() external view override returns (address, uint256) { + DataTypes.ReserveData memory data = IPool(strategyAddr).getReserveData(TOKEN_ADDRESS); + return (TOKEN_ADDRESS, IERC20(data.aTokenAddress).balanceOf(address(this))); + } } diff --git a/src/vault/simpleVault.sol b/src/vault/simpleVault.sol index 0193aaa..b9672e4 100644 --- a/src/vault/simpleVault.sol +++ b/src/vault/simpleVault.sol @@ -58,4 +58,8 @@ contract SimpleVault is VaultCore { function _decodeExitData(bytes calldata data) internal pure returns (uint256) { return abi.decode(data, (uint256)); } + + function getPosition() external view override returns (address, uint256) { + return (STABLE_TOKEN_ADDRESS, IERC20(STABLE_TOKEN_ADDRESS).balanceOf(address(this))); + } } diff --git a/src/vault/uniswapV2Vault.sol b/src/vault/uniswapV2Vault.sol index 85b13e3..2cd2411 100644 --- a/src/vault/uniswapV2Vault.sol +++ b/src/vault/uniswapV2Vault.sol @@ -76,4 +76,9 @@ contract UniV2Vault is VaultCore { function _decodeExitData(bytes calldata data) internal pure returns (uint256) { return abi.decode(data, (uint256)); } + + function getPosition() external view override returns (address, uint256) { + return (PAIR_ADDRESS, IERC20(PAIR_ADDRESS).balanceOf(address(this))); + } + } diff --git a/test/vault/PellVault.Fork.t.sol b/test/vault/PellVault.Fork.t.sol new file mode 100644 index 0000000..5e6aec3 --- /dev/null +++ b/test/vault/PellVault.Fork.t.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {Test, console} from "forge-std/Test.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {PellVault} from "../../src/vault/PellVault.sol"; +import {SatoshiCore} from "../../src/core/SatoshiCore.sol"; +import {VaultManager} from "../../src/vault/VaultManager.sol"; +import {TroveManager} from "../../src/core/TroveManager.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ICDPVault} from "../../src/interfaces/vault/ICDPVault.sol"; +import {ISatoshiCore} from "../../src/interfaces/core/ISatoshiCore.sol"; +import {ITroveManager} from "../../src/interfaces/core/ITroveManager.sol"; +import {INYMVault} from "../../src/interfaces/vault/INYMVault.sol"; +import {IVaultManager} from "../../src/interfaces/vault/IVaultManager.sol"; + +contract PellVaultTest is Test { + address constant tokenAddress = 0x03C7054BCB39f7b2e5B2c7AcB37583e32D70Cfa3; // WBTC + address constant OWNER = 0x6510b482312e528fbb892b7C3A0d29e07E12DEc3; + address constant deployer = 0x15F1fDBA8C05B594eFfDE3CCdaC0133981eD18b7; + address constant whale = 0xBDFedF992128CbF10974DC935976116e10665Cc9; // TroveManager + address constant lendingPool = 0x00B67E4805138325ce871D5E27DC15f994681bC1; // StrategyManagerV2 + address constant pellStrategy = 0x92D374dd17F8416c8129f5Efa81f28E0926a60B7; + address constant avalonVault = 0x713dD0E14376a6d34D0Fde2783dca52c9fD852bA; + ISatoshiCore satoshiCore = ISatoshiCore(0xd6dBF24f3516844b02Ad8d7DaC9656F2EC556639); + ITroveManager troveManager = ITroveManager(whale); + IVaultManager vaultManagerProxy = IVaultManager(0x6a67CFD9b8800B7d1093E4C7e84E5346ec88240F); + PellVault pellVault; + + function setUp() public { + vm.createSelectFork(vm.envString("BOB_RPC_URL")); + + vm.startPrank(deployer); + _deployPellVault(); + vm.stopPrank(); + + vm.startPrank(OWNER); + + pellVault.setStrategyAddr(lendingPool); + + INYMVault[] memory vaults = new INYMVault[](2); + vaults[0] = INYMVault(address(pellVault)); + vaults[1] = INYMVault(avalonVault); + _setVaultManagerWL(vaults); + + vm.stopPrank(); + } + + function _deployVaultManager() internal returns (address) { + VaultManager vaultManagerImpl = new VaultManager(); + assert(vaultManagerProxy == IVaultManager(address(0))); + bytes memory data = abi.encodeCall(IVaultManager.initialize, (satoshiCore, address(troveManager))); + vaultManagerProxy = IVaultManager(address(new ERC1967Proxy(address(vaultManagerImpl), data))); + + return address(vaultManagerProxy); + } + + function _deployPellVault() internal returns (address) { + PellVault pellVaultImpl = new PellVault(); + + bytes memory initializeData = abi.encode(satoshiCore, tokenAddress, address(vaultManagerProxy), pellStrategy); + bytes memory data = abi.encodeCall(ICDPVault.initialize, (initializeData)); + address proxy = address(new ERC1967Proxy(address(pellVaultImpl), data)); + pellVault = PellVault(proxy); + + return address(pellVault); + } + + function _setCDPFarming() internal { + troveManager.setFarmingParams(3000, 3500); + troveManager.setVaultManager(address(vaultManagerProxy)); + } + + function _setVaultManagerWL(INYMVault[] memory vaults) internal { + for (uint256 i = 0; i < vaults.length; i++) { + vaultManagerProxy.setWhiteListVault(address(vaults[i]), true); + } + vaultManagerProxy.setPriority(vaults); + } + + function test_executeAndExitStrategyPell_VaultManager() public { + uint256 farmingAmount = 0.0005e8; + vm.startPrank(OWNER); + troveManager.transferCollToPrivilegedVault(address(vaultManagerProxy), farmingAmount); + assertEq(IERC20(tokenAddress).balanceOf(address(vaultManagerProxy)), farmingAmount); + + vaultManagerProxy.executeStrategy(address(pellVault), farmingAmount); + assertEq(IERC20(tokenAddress).balanceOf(address(vaultManagerProxy)), 0); + (address asset, uint256 balance) = pellVault.getPosition(); + assertEq(balance, farmingAmount); + assertEq(asset, tokenAddress); + + vaultManagerProxy.exitStrategy(address(pellVault), farmingAmount); + // assertEq(IERC20(tokenAddress).balanceOf(address(vaultManagerProxy)), farmingAmount); + vm.stopPrank(); + } +} diff --git a/test/vault/aaveVault.t.sol b/test/vault/aaveVault.t.sol index 7b8de5f..c9337b4 100644 --- a/test/vault/aaveVault.t.sol +++ b/test/vault/aaveVault.t.sol @@ -40,6 +40,9 @@ contract AAVEVaultTest is Test { vm.startPrank(owner); bytes memory data = abi.encode(100); aaveVault.executeStrategy(data); + (address asset, uint256 balance) = aaveVault.getPosition(); + assertEq(balance, 100); + assertEq(asset, stableTokenAddress); aaveVault.exitStrategy(data); assertEq(IERC20(stableTokenAddress).balanceOf(owner), 100); vm.stopPrank(); diff --git a/test/vault/avalonVault.Fork.t.sol b/test/vault/avalonVault.Fork.t.sol index e9b9101..cbd27de 100644 --- a/test/vault/avalonVault.Fork.t.sol +++ b/test/vault/avalonVault.Fork.t.sol @@ -97,7 +97,10 @@ contract AvalonVaultTest is Test { assertEq(IERC20(tokenAddress).balanceOf(address(vaultManagerProxy)), farmingAmount); vaultManagerProxy.executeStrategy(address(avalonVault), farmingAmount); + (address asset, uint256 balance) = avalonVault.getPosition(); assertEq(IERC20(tokenAddress).balanceOf(address(vaultManagerProxy)), 0); + assertEq(balance, farmingAmount); + assertEq(asset, tokenAddress); vaultManagerProxy.exitStrategy(address(avalonVault), farmingAmount); assertEq(IERC20(tokenAddress).balanceOf(address(vaultManagerProxy)), farmingAmount);