From 45b5d6b48da5b875dc9b7997275f8708d9e41656 Mon Sep 17 00:00:00 2001 From: Schlag <89420541+Schlagonia@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:21:15 -0600 Subject: [PATCH] feat: v3.0.1 (#183) * feat: vault interface * chore: solidity interfaces * feat: solidity interface * feat: add deployment script * feat: increase version * chore: script interface * chore: update spech * chore: rebase to limit merge * fix: black * chore: bump sol * fix: read me and security doc * fix: interface add constants * chore: sol version * fix: spelling --- .solhint.json | 4 +- README.md | 3 +- SECURITY.md | 9 + TECH_SPEC.md | 64 +++-- contracts/VaultFactory.vy | 18 +- contracts/VaultV3.vy | 32 +-- contracts/interfaces/IDeployer.sol | 8 + contracts/interfaces/IStrategyERC4626.sol | 25 -- contracts/interfaces/IVault.sol | 244 +++++++++++++++++- contracts/interfaces/IVaultFactory.sol | 55 ++++ contracts/interfaces/VaultConstants.sol | 32 +++ contracts/test/ERC4626BaseStrategy.sol | 31 +-- contracts/test/Token.sol | 2 +- .../test/mocks/ERC4626/BaseStrategyMock.sol | 10 +- .../test/mocks/ERC4626/FaultyStrategy.sol | 2 +- contracts/test/mocks/ERC4626/Generic4626.sol | 2 +- .../test/mocks/ERC4626/LiquidStrategy.sol | 9 +- .../test/mocks/ERC4626/LockedStrategy.sol | 10 +- .../test/mocks/ERC4626/LossyStrategy.sol | 30 +-- contracts/test/mocks/periphery/Accountant.vy | 7 +- .../test/mocks/periphery/FaultyAccountant.vy | 7 +- .../mocks/periphery/FlexibleAccountant.vy | 7 +- contracts/test/mocks/periphery/LimitModule.vy | 12 +- package.json | 2 +- scripts/deploy.py | 95 +++++++ tests/utils/constants.py | 1 + yarn.lock | 8 +- 27 files changed, 554 insertions(+), 175 deletions(-) create mode 100644 SECURITY.md create mode 100644 contracts/interfaces/IDeployer.sol delete mode 100644 contracts/interfaces/IStrategyERC4626.sol create mode 100644 contracts/interfaces/IVaultFactory.sol create mode 100644 contracts/interfaces/VaultConstants.sol create mode 100644 scripts/deploy.py diff --git a/.solhint.json b/.solhint.json index e4d26503..c35b3e40 100644 --- a/.solhint.json +++ b/.solhint.json @@ -2,10 +2,12 @@ "extends": "solhint:recommended", "plugins": ["prettier", "yearn"], "rules": { - "compiler-version": ["error", "0.8.14"], + "compiler-version": ["error", "0.8.18"], "code-complexity": "warn", "const-name-snakecase": "warn", "function-max-lines": "warn", + "func-name-mixedcase": "off", + "var-name-mixedcase": "off", "func-visibility": ["warn", { "ignoreConstructors": true }], "max-line-length": ["warn", 160], "avoid-suicide": "error", diff --git a/README.md b/README.md index 263fe650..3bfa0e22 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This repository runs on [ApeWorx](https://www.apeworx.io/). A python based devel You will need: - Python 3.8 or later + - Vyper 0.3.7 - Linux or macOS - Windows: Install Windows Subsystem Linux (WSL) with Python 3.8 or later - [Hardhat](https://hardhat.org/) installed globally @@ -63,4 +64,4 @@ ape test See the ApeWorx [documentation](https://docs.apeworx.io/ape/stable/) and [github](https://github.com/ApeWorX/ape) for more information. -You will need hardhat to run the test `yarn` +You will need hardhat to run the test `yarn` \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..6ca7c29d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security + +For full security process refer to yearn-security [repo](https://github.com/yearn/yearn-security/blob/master/SECURITY.md). + +## Scope + +The scope of the Bug Bounty program spans smart contracts utilized in the Yearn ecosystem – including but not limited to the main [VaultV3.vy](https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy) and [VaultFactory.vy](https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy) Vyper contracts in this repo, including historical deployments that still see active use associated with Yearn, and excluding any contracts used in a test-only capacity (including test-only deployments). + +Note: Other contracts, outside of the ones mentioned above, might be considered on a case by case basis, please, reach out to the Yearn development team for clarification. \ No newline at end of file diff --git a/TECH_SPEC.md b/TECH_SPEC.md index 3a2fbb0b..3c1fd131 100644 --- a/TECH_SPEC.md +++ b/TECH_SPEC.md @@ -8,6 +8,7 @@ - Vault: ERC4626 compliant Smart contract that receives Assets from Depositors to then distribute them among the different Strategies added to the vault, managing accounting and Assets distribution. - Role: the different flags an Account can have in the Vault so that the Account can do certain specific actions. Can be fulfilled by a smart contract or an EOA. - Accountant: smart contract that receives P&L reporting and returns shares and refunds to the strategy +- Limit Modules: Add on smart contracts that can control the vaults deposit and withdraw limits dynamically. # VaultV3 Specification The Vault code has been designed as an non-opinionated system to distribute funds of depositors into different opportunities (aka Strategies) and manage accounting in a robust way. That's all. @@ -27,8 +28,9 @@ This allows different players to deploy their own version and implement their ow ``` Example periphery contracts: - Emergency module: it receives deposits of Vault Shares and allows the contract to call the shutdown function after a certain % of total Vault Shares have been deposited -- Debt Allocator: a smart contract that incentivises APY / debt allocation optimisation by rewarding the best debt allocation (see [yStarkDebtAllocator](https://github.com/jmonteer/ystarkdebtallocator)) -- Strategy Staking Module: a smart contract that allows players to sponsor specific strategies (so that they are added to the vault) by staking their YFI, making money if they do well and losing money if they don't +- Debt Allocator: a smart contract that incentivize's APY / debt allocation optimization by rewarding the best debt allocation (see [yStarkDebtAllocator](https://github.com/jmonteer/ystarkdebtallocator)) +- Strategy Staking Module: a smart contract that allows players to sponsor specific strategies (so that they are added to the vault) by staking their YFI, making money if they do well and losing money if they don't. +- Deposit Limit Module: Will dynamically adjust the deposit limit based on the depositor and arbitrary conditions. - ... ``` ## Deployment @@ -48,16 +50,16 @@ When deploying a new vault, it requires the following parameters: ### Deposits / Mints Users can deposit ASSET tokens to receive yvTokens (SHARES). -Deposits are limited under depositLimit and shutdown parameters. Read below for details. +Deposits are limited under depositLimit/depositLimitModule and shutdown parameters. Read below for details. ### Withdrawals / Redeems Users can redeem their shares at any point in time if there is liquidity available. -Optionally, a user can specify a list of strategies to withdraw from. If a list of strategies is passed, the vault will try to withdraw from them. +Optionally, if the vault management allows, a user can specify a list of strategies to withdraw from. If a list of strategies is passed, the vault will try to withdraw from them. -If a user passed array is not defined, the redeem function will use the default_queue. +If a user passed array is not defined or the use_default_queue flag has been turned on, the redeem function will use the default_queue. -In order to properly comply with the ERC-4626 standard and still allow losses, both withdraw and redeem have an additional optional parameter of 'maxLoss' that can be used. The default for 'maxLoss' is 0 (i.e. revert if any loss) for withdraws, and 10_000 (100%) for redeems. +In order to properly comply with the ERC-4626 standard and still allow losses, both withdraw and redeem have an additional optional parameter of 'max_loss' that can be used. The default for 'max_loss' is 0 (i.e. revert if any loss) for withdraws, and 10_000 (100%) for redeems. If not enough funds have been recovered to honor the full request within the maxLoss, the transaction will revert. @@ -114,7 +116,8 @@ These are: - REPORTING_MANAGER: role that calls report for strategies - DEBT_MANAGER: role that adds and removes debt from strategies - MAX_DEBT_MANAGER: role that can set the max debt for a strategy -- DEPOSIT_LIMIT_MANAGER: role that sets deposit limit for the vault +- DEPOSIT_LIMIT_MANAGER: role that sets deposit limit or deposit limit module for the vault +- WITHDRAW_LIMIT_MANAGER: role that sets the withdraw limit module for the vault. - MINIMUM_IDLE_MANAGER: role that sets the minimum total idle the vault should keep - PROFIT_UNLOCK_MANAGER: role that sets the profit_max_unlock_time - DEBT_PURCHASER # can purchase bad debt from the vault @@ -126,6 +129,10 @@ The account that manages roles is a single account, set in `role_manager`. This role_manager can be an EOA, a multi-sig or a Governance Module that relays calls. +The vault comes with the ability to "open" every role. Meaning that any function that requires the caller to hold that role would be come permsissionless. + +The vault imposes no restrictions on the role managers ability to open or close any role. **But this should be done with extreme care as most of the roles are not meant to be opened and can lead to loss of funds if done incorrectly**. + ### Strategy Management This responsibility is taken by callers with ADD_STRATEGY_MANAGER, REVOKE_STRATEGY_MANAGER and FORCE_REVOKE_MANAGER roles @@ -137,10 +144,14 @@ Revoked strategies will return all debt and stop being eligible to receive more. Force revoking a strategy is only used in cases of a faulty strategy that cannot otherwise have its current_debt reduced to 0. Force revoking a strategy will result in a loss being reported by the vault. -#### Setting the periphery contracts -The accountant can each be set by the ACCOUNTANT_MANAGER. +#### Setting the modules/periphery contracts +The accountant can be set by the ACCOUNTANT_MANAGER. + +A deposit_limit_module can be set by the DEPOSIT_LIMIT_MANAGER -The contract is not needed for the vault to function but are recommended for optimal use. +A withdraw_limit_module can be set by the WITHDRAW_LIMIT_MANAGER + +These contracts are not needed for the vault to function but are optional add ons for optimal use. #### Reporting profits The REPORTING_MANAGER is in charge of calling process_report() for each strategy in the vault according to its own timeline @@ -168,10 +179,17 @@ Stored in strategies[strategy].max_debt When a debt re-balance is triggered, the Vault will cap the new target debt to this number (max_debt) #### Setting the deposit limit -The DEPOSIT_LIMIT_MANAGER is in charge of setting the deposit_limit for the vault +The DEPOSIT_LIMIT_MANAGER is in charge of setting the deposit_limit or a deposit_limit_module for the vault On deployment deposit_limit defaults to 0 and will need to be increased to make the vault functional +The deposit_limit will have to be set to MAX_UINT256 in order to set a deposit_limit_module, and the module will have to be address 0 to adjust the deposit_limit. + +#### Setting the withdraw limit module +The WITHDRAW_LIMIT_MANAGER is in charge of setting the withdraw_limit_module for the vault + +The vaults default withdraw limit is calculated based on the liquidity of its strategies. Setting a withdraw limit module will override this functionality. + #### Setting minimum idle funds The MINIMUM_IDLE_MANAGER can specify how many funds the vault should try to have reserved to serve withdrawal requests @@ -189,13 +207,12 @@ The QUEUE_MANAGER has the option to set a custom default_queue if desired. The v All strategies in the default queue must have been previously added to the vault. -#### Buying Debt -The DEBT_PURCHASER role can buy debt from the vault in return for the equal amount of `asset`. - -This should only ever be used in the case where governance wants to purchase a set amount of bade debt from the vault in order to not report a loss. +The QUEUE_MANAGER can also set the use_default_queue flag, which will cause the default_queue to be used during every withdraw even if a custom_queue is passed in. -It still relies on convertToShares() so will only be viable if the conversion does not reflect and large negative realized loss from the strategy. +#### Buying Debt +The DEBT_PURCHASER role can buy bad debt from the vault in return for the equal amount of `asset`. +This should only ever be used in emergencies where governance wants to purchase a set amount of bad debt from the vault in order to not report a loss. #### Shutting down the vault In an emergency the EMERGENCY_MANAGER can shutdown the vault @@ -203,20 +220,19 @@ In an emergency the EMERGENCY_MANAGER can shutdown the vault This will also give the EMERGENCY_MANAGER the DEBT_MANAGER roles as well so funds can start to be returned from the strategies ## Strategy Minimum API -Strategies are completely independent smart contracts that can be implemented following the proposed template or in any other way. +Strategies are completely independent smart contracts that can be implemented following the [Tokenized Strategy](https://github.com/yearn/tokenized-strategy) template or in any other way. In any case, to be compatible with the vault, they need to implement the following functions, which are a subset of ERC4626 vaults: - asset(): view returning underlying asset - totalAssets(): view returning current amount of assets. It can include rewards valued in `asset` ¡ - maxDeposit(address): view returning the amount max that the strategy can take safely - deposit(assets, receiver): deposits `assets` amount of tokens into the strategy. it can be restricted to vault only or be open -- maxWithdraw(address): view returning how many asset can the vault take from the vault at any given point in time -- withdraw(assets, receiver, owner): withdraws `assets` amount of tokens from the strategy - redeem(shares, receiver, owner): redeems `shares` of the strategy for the underlying asset. - balanceOf(address): return the number of shares of the strategy that the address has -- convertToAssets(shares: uint256): Converts `shares` into the corresponding amount of asset. -- convertToShares(assets: uint256): Converts `assets` into the corresponding amount of shares. -- previewWithdraw(assets: uint256): Converst `assets` into the corresponding amount of shares rounding up. +- convertToAssets(shares): Converts `shares` into the corresponding amount of asset. +- convertToShares(assets): Converts `assets` into the corresponding amount of shares. +- previewWithdraw(assets): Converts `assets` into the corresponding amount of shares rounding up. +- maxRedeem(owner): return the max amount of shares that `owner` can redeem. This means that the vault can deposit into any ERC4626 vault but also that a non-compliant strategy can be implemented provided that these functions have been implemented (even in a non ERC4626 compliant way). @@ -228,6 +244,8 @@ The most important implication is that `withdraw` and `redeem` functions as pres 1. max_loss: The amount in basis points that the withdrawer will accept as a loss. I.E. 100 = 1% loss accepted. 2. strategies: This is an array of strategies to use as the withdrawal queue instead of the default queue. +* `maxWithdraw` and `maxRedeem` also come with both of these optional parameters to get the most exact amounts. + ### Shutdown mode In the case the current roles stop fulfilling their responsibilities or something else happens, the EMERGENCY_MANAGER can shutdown the vault. @@ -244,7 +262,7 @@ Withdrawals can't be paused under any circumstance ### Accounting Shutdown mode does not affect accounting -### Debt rebalance +### Debt re-balance _Light emergency_: Setting minimumTotalIdle to MAX_UINT256 will result in the vault requesting the debt back from strategies. This would stop new strategies from getting funded too, as the vault prioritizes minimumTotalIdle _Shutdown mode_: All strategies maxDebt is set to 0. Strategies will return funds as soon as they can. diff --git a/contracts/VaultFactory.vy b/contracts/VaultFactory.vy index 90d717ba..cf79c3fa 100644 --- a/contracts/VaultFactory.vy +++ b/contracts/VaultFactory.vy @@ -6,7 +6,7 @@ @author yearn.finance @notice This vault Factory can be used by anyone wishing to deploy their own - ERC4626 compliant Vault. + ERC4626 compliant Yearn V3 Vault of the same API version. The factory uses the Blueprint (ERC-5202) standard to handle the deployment of any new vaults off of the immutable address stored @@ -14,7 +14,7 @@ initialized fully on-chain with their init byte code, thus not requiring any delegatecall patterns or post deployment initialization. The deployments are done through create2 with a specific `salt` - that is derived from a combination of the deployers address, + that is derived from a combination of the deployer's address, the underlying asset used, as well as the name and symbol specified. Meaning a deployer will not be able to deploy the exact same vault twice and will need to use different name and or symbols for vaults @@ -23,9 +23,9 @@ The factory also holds the protocol fee configs for each vault and strategy of its specific `API_VERSION` that determine how much of the fees charged are designated "protocol fees" and sent to the designated - `fee_recipient`. The protocol fees work through rev share system, - where if the vault or strategy determines to charge X amount of total - fees during a `report` the protocol fees are X * fee_bps / 10_000. + `fee_recipient`. The protocol fees work through a revenue share system, + where if the vault or strategy decides to charge X amount of total + fees during a `report` the protocol fees are a percent of X. The protocol fees will be sent to the designated fee_recipient and then (X - protocol_fees) will be sent to the vault/strategy specific fee recipient. @@ -68,7 +68,7 @@ struct PFConfig: fee_recipient: address # Identifier for this version of the vault. -API_VERSION: constant(String[28]) = "3.0.0" +API_VERSION: constant(String[28]) = "3.0.1" # The max amount the protocol fee can be set to. MAX_FEE_BPS: constant(uint16) = 5_000 # 50% @@ -178,7 +178,7 @@ def set_protocol_fee_bps(new_protocol_fee_bps: uint16): """ @notice Set the protocol fee in basis points @dev Must be below the max allowed fee, and a default - fee_recipient must be set so we don't issue fees to the 0 addresss. + fee_recipient must be set so we don't issue fees to the 0 address. @param new_protocol_fee_bps The new protocol fee in basis points """ assert msg.sender == self.governance, "not governance" @@ -196,7 +196,7 @@ def set_protocol_fee_bps(new_protocol_fee_bps: uint16): def set_protocol_fee_recipient(new_protocol_fee_recipient: address): """ @notice Set the protocol fee recipient - @dev Can never be set to 0 to avoid issuing fees to the 0 addresss. + @dev Can never be set to 0 to avoid issuing fees to the 0 address. @param new_protocol_fee_recipient The new protocol fee recipient """ assert msg.sender == self.governance, "not governance" @@ -215,7 +215,7 @@ def set_custom_protocol_fee_bps(vault: address, new_custom_protocol_fee: uint16) @notice Allows Governance to set custom protocol fees for a specific vault or strategy. @dev Must be below the max allowed fee, and a default - fee_recipient must be set so we don't issue fees to the 0 addresss. + fee_recipient must be set so we don't issue fees to the 0 address. @param vault The address of the vault or strategy to customize. @param new_custom_protocol_fee The custom protocol fee in BPS. """ diff --git a/contracts/VaultV3.vy b/contracts/VaultV3.vy index b405b6c6..ebd1beef 100644 --- a/contracts/VaultV3.vy +++ b/contracts/VaultV3.vy @@ -174,7 +174,7 @@ MAX_BPS: constant(uint256) = 10_000 # Extended for profit locking calculations. MAX_BPS_EXTENDED: constant(uint256) = 1_000_000_000_000 # The version of this vault. -API_VERSION: constant(String[28]) = "3.0.0" +API_VERSION: constant(String[28]) = "3.0.1" # ENUMS # # Each permissioned function has its own Role. @@ -427,7 +427,7 @@ def _unlocked_shares() -> uint256: @view @internal def _total_supply() -> uint256: - # Need to account for the shares issued to the vault that have unlockded. + # Need to account for the shares issued to the vault that have unlocked. return self.total_supply - self._unlocked_shares() @internal @@ -551,7 +551,7 @@ def _issue_shares_for_amount(amount: uint256, recipient: address) -> uint256: # If total_supply > 0 but amount = totalAssets we want to revert because # after first deposit, getting here would mean that the rest of the shares # would be diluted to a price_per_share of 0. Issuing shares would then mean - # either the new depositer or the previous depositers will loose money. + # either the new depositor or the previous depositors will loose money. assert total_assets > amount, "amount too high" # We don't make the function revert @@ -628,7 +628,7 @@ def _max_withdraw( # Cache the default queue. _strategies: DynArray[address, MAX_QUEUE] = self.default_queue - # If a custom queue was passed, and we dont force the default queue. + # If a custom queue was passed, and we don't force the default queue. if len(strategies) != 0 and not self.use_default_queue: # Use the custom queue. _strategies = strategies @@ -682,7 +682,7 @@ def _max_withdraw( loss += unrealised_loss # Update the max after going through the queue. - # In case we broke early or exausted the queue. + # In case we broke early or exhausted the queue. max_assets = have return max_assets @@ -770,7 +770,7 @@ def _withdraw_from_strategy(strategy: address, assets_to_withdraw: uint256): This takes the amount denominated in asset and performs a {redeem} with the corresponding amount of shares. - We use {redeem} to natively take on losses without aditional non-4626 standard parameters. + We use {redeem} to natively take on losses without additional non-4626 standard parameters. """ # Need to get shares since we use redeem to be able to take on losses. shares_to_redeem: uint256 = min( @@ -796,7 +796,7 @@ def _redeem( This will attempt to free up the full amount of assets equivalent to `shares_to_burn` and transfer them to the `receiver`. If the vault does not have enough idle funds it will go through any strategies provided by - either the withdrawer or the queue_manaager to free up enough funds to + either the withdrawer or the queue_manager to free up enough funds to service the request. The vault will attempt to account for any unrealized losses taken on from @@ -834,7 +834,7 @@ def _redeem( # Cache the default queue. _strategies: DynArray[address, MAX_QUEUE] = self.default_queue - # If a custom queue was passed, and we dont force the default queue. + # If a custom queue was passed, and we don't force the default queue. if len(strategies) != 0 and not self.use_default_queue: # Use the custom queue. _strategies = strategies @@ -882,7 +882,7 @@ def _redeem( wanted: uint256 = assets_to_withdraw - unrealised_losses_share # Get the proportion of unrealised comparing what we want vs. what we can get unrealised_losses_share = unrealised_losses_share * max_withdraw / wanted - # Adjust assets_to_withdraw so all future calcultations work correctly + # Adjust assets_to_withdraw so all future calculations work correctly assets_to_withdraw = max_withdraw + unrealised_losses_share # User now "needs" less assets to be unlocked (as he took some as losses) @@ -920,7 +920,7 @@ def _redeem( loss: uint256 = 0 # Check if we redeemed too much. if withdrawn > assets_to_withdraw: - # Make sure we don't underlfow in debt updates. + # Make sure we don't underflow in debt updates. if withdrawn > current_debt: # Can't withdraw more than our debt. assets_to_withdraw = current_debt @@ -1014,7 +1014,7 @@ def _revoke_strategy(strategy: address, force: bool=False): log StrategyReported(strategy, 0, loss, 0, 0, 0, 0) - # Set strategy params all back to 0 (WARNING: it can be readded). + # Set strategy params all back to 0 (WARNING: it can be re-added). self.strategies[strategy] = StrategyParams({ activation: 0, last_report: 0, @@ -1187,7 +1187,7 @@ def _process_report(strategy: address) -> (uint256, uint256): # Burn shares that have been unlocked since the last update self._burn_unlocked_shares() - # Vault asseses profits using 4626 compliant interface. + # Vault assesses profits using 4626 compliant interface. # NOTE: It is important that a strategies `convertToAssets` implementation # cannot be manipulated or else the vault could report incorrect gains/losses. strategy_shares: uint256 = IStrategy(strategy).balanceOf(self) @@ -1229,7 +1229,7 @@ def _process_report(strategy: address) -> (uint256, uint256): # Protocol fees are a percent of the fees the accountant is charging. protocol_fees = total_fees * convert(protocol_fee_bps, uint256) / MAX_BPS - # `shares_to_burn` is derived from amounts that would reduce the vaullts PPS. + # `shares_to_burn` is derived from amounts that would reduce the vaults PPS. # NOTE: this needs to be done before any pps changes shares_to_burn: uint256 = 0 accountant_fees_shares: uint256 = 0 @@ -1273,12 +1273,12 @@ def _process_report(strategy: address) -> (uint256, uint256): self.total_debt -= loss # NOTE: should be precise (no new unlocked shares due to above's burn of shares) - # newly_locked_shares have already been minted / transfered to the vault, so they need to be substracted + # newly_locked_shares have already been minted / transferred to the vault, so they need to be subtracted # no risk of underflow because they have just been minted. previously_locked_shares: uint256 = self.balance_of[self] - newly_locked_shares # Now that pps has updated, we can burn the shares we intended to burn as a result of losses/fees. - # NOTE: If a value reduction (losses / fees) has occured, prioritize burning locked profit to avoid + # NOTE: If a value reduction (losses / fees) has occurred, prioritize burning locked profit to avoid # negative impact on price per share. Price per share is reduced only if losses exceed locked value. if shares_to_burn > 0: # Cant burn more than the vault owns. @@ -1584,7 +1584,7 @@ def pricePerShare() -> uint256: @view @external -def get_default_queue() -> DynArray[address, 10]: +def get_default_queue() -> DynArray[address, MAX_QUEUE]: """ @notice Get the full default queue currently set. @return The current default withdrawal queue. diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol new file mode 100644 index 00000000..ed925479 --- /dev/null +++ b/contracts/interfaces/IDeployer.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +interface IDeployer { + event Deployed(address addr, uint256 salt); + + function deploy(bytes memory code, uint256 salt) external; +} diff --git a/contracts/interfaces/IStrategyERC4626.sol b/contracts/interfaces/IStrategyERC4626.sol deleted file mode 100644 index 71116e62..00000000 --- a/contracts/interfaces/IStrategyERC4626.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; - -interface IStrategyERC4626 { - // So indexers can keep track of this - // ****** EVENTS ****** - - error NoAccess(); - - error ProtectedToken(address token); - - error StrategyAlreadyInitialized(); - - function vault() external view returns (address _vault); - - // - `invest()`: strategy will invest loose funds into the strategy. only callable by keepers - function invest() external; - - // - `freeFunds(uint256 _amount)`: strategy will free/unlocked funds from the underlying protocol and leave them idle. (called by vault on update_debt) - function freeFunds(uint256 _amount) external returns (uint256 _freeFunds); - - function delegatedAssets() external view returns (uint256 _delegatedAssets); - - function migrate(address) external; -} diff --git a/contracts/interfaces/IVault.sol b/contracts/interfaces/IVault.sol index 659fe157..d7cecd7b 100644 --- a/contracts/interfaces/IVault.sol +++ b/contracts/interfaces/IVault.sol @@ -1,6 +1,244 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; -interface IVault { - function asset() external view returns (address _asset); +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; + +interface IVault is IERC4626 { + // STRATEGY EVENTS + event StrategyChanged(address indexed strategy, uint256 change_type); + event StrategyReported( + address indexed strategy, + uint256 gain, + uint256 loss, + uint256 current_debt, + uint256 protocol_fees, + uint256 total_fees, + uint256 total_refunds + ); + // DEBT MANAGEMENT EVENTS + event DebtUpdated( + address indexed strategy, + uint256 current_debt, + uint256 new_debt + ); + // ROLE UPDATES + event RoleSet(address indexed account, uint256 role); + event RoleStatusChanged(uint256 role, uint256 status); + event UpdateRoleManager(address indexed role_manager); + + event UpdateAccountant(address indexed accountant); + event UpdateDefaultQueue(address[] new_default_queue); + event UpdateUseDefaultQueue(bool use_default_queue); + event UpdatedMaxDebtForStrategy( + address indexed sender, + address indexed strategy, + uint256 new_debt + ); + event UpdateDepositLimit(uint256 deposit_limit); + event UpdateMinimumTotalIdle(uint256 minimum_total_idle); + event UpdateProfitMaxUnlockTime(uint256 profit_max_unlock_time); + event DebtPurchased(address indexed strategy, uint256 amount); + event Shutdown(); + + struct StrategyParams { + uint256 activation; + uint256 last_report; + uint256 current_debt; + uint256 max_debt; + } + + function FACTORY() external view returns (uint256); + + function strategies(address) external view returns (StrategyParams memory); + + function default_queue(uint256) external view returns (address); + + function use_default_queue() external view returns (bool); + + function total_supply() external view returns (uint256); + + function minimum_total_idle() external view returns (uint256); + + function deposit_limit() external view returns (uint256); + + function deposit_limit_module() external view returns (address); + + function withdraw_limit_module() external view returns (address); + + function accountant() external view returns (address); + + function roles(address) external view returns (uint256); + + function open_roles(uint256) external view returns (bool); + + function role_manager() external view returns (address); + + function future_role_manager() external view returns (address); + + function shutdown() external view returns (bool); + + function nonces(address) external view returns (uint256); + + function set_accountant(address new_accountant) external; + + function set_default_queue(address[] memory new_default_queue) external; + + function set_use_default_queue(bool) external; + + function set_deposit_limit(uint256 deposit_limit) external; + + function set_deposit_limit_module( + address new_deposit_limit_module + ) external; + + function set_withdraw_limit_module( + address new_withdraw_limit_module + ) external; + + function set_minimum_total_idle(uint256 minimum_total_idle) external; + + function set_profit_max_unlock_time( + uint256 new_profit_max_unlock_time + ) external; + + function set_role(address account, uint256 role) external; + + function add_role(address account, uint256 role) external; + + function remove_role(address account, uint256 role) external; + + function set_open_role(uint256 role) external; + + function close_open_role(uint256 role) external; + + function transfer_role_manager(address role_manager) external; + + function accept_role_manager() external; + + function unlocked_shares() external view returns (uint256); + + function pricePerShare() external view returns (uint256); + + function get_default_queue() external view returns (address[] memory); + + function process_report( + address strategy + ) external returns (uint256, uint256); + + function buy_debt(address strategy, uint256 amount) external; + + function add_strategy(address new_strategy) external; + + function revoke_strategy(address strategy) external; + + function force_revoke_strategy(address strategy) external; + + function update_max_debt_for_strategy( + address strategy, + uint256 new_max_debt + ) external; + + function update_debt( + address strategy, + uint256 target_debt + ) external returns (uint256); + + function shutdown_vault() external; + + function totalIdle() external view returns (uint256); + + function totalDebt() external view returns (uint256); + + function api_version() external view returns (string memory); + + function assess_share_of_unrealised_losses( + address strategy, + uint256 assets_needed + ) external view returns (uint256); + + function profitMaxUnlockTime() external view returns (uint256); + + function fullProfitUnlockDate() external view returns (uint256); + + function profitUnlockingRate() external view returns (uint256); + + function lastProfitUpdate() external view returns (uint256); + + //// NON-STANDARD ERC-4626 FUNCTIONS \\\\ + + function withdraw( + uint256 assets, + address receiver, + address owner, + uint256 max_loss + ) external returns (uint256); + + function withdraw( + uint256 assets, + address receiver, + address owner, + uint256 max_loss, + address[] memory strategies + ) external returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner, + uint256 max_loss + ) external returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner, + uint256 max_loss, + address[] memory strategies + ) external returns (uint256); + + function maxWithdraw( + address owner, + uint256 max_loss + ) external view returns (uint256); + + function maxWithdraw( + address owner, + uint256 max_loss, + address[] memory strategies + ) external view returns (uint256); + + function maxRedeem( + address owner, + uint256 max_loss + ) external view returns (uint256); + + function maxRedeem( + address owner, + uint256 max_loss, + address[] memory strategies + ) external view returns (uint256); + + //// NON-STANDARD ERC-20 FUNCTIONS \\\\ + + function increaseAllowance( + address spender, + uint256 amount + ) external returns (bool); + + function decreaseAllowance( + address spender, + uint256 amount + ) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function permit( + address owner, + address spender, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (bool); } diff --git a/contracts/interfaces/IVaultFactory.sol b/contracts/interfaces/IVaultFactory.sol new file mode 100644 index 00000000..ff476a5d --- /dev/null +++ b/contracts/interfaces/IVaultFactory.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.18; + +interface IVaultFactory { + event NewVault(address indexed vaultAddress, address indexed asset); + event UpdateProtocolFeeBps( + uint16 oldProtocolFeeBps, + uint16 newProtocolFeeBps + ); + event UpdateProtocolFeeRecipient( + address oldProtocolFeeRecipient, + address newProtocolFeeRecipient + ); + event UpdateCustomProtocolFee(address vault, uint16 newCustomProtocolFee); + event RemovedCustomProtocolFee(address vault); + event FactoryShutdown(); + event NewPendingGovernance(address newPendingGovernance); + event UpdateGovernance(address newGovernance); + + function deploy_new_vault( + address asset, + string memory name, + string memory symbol, + address role_manager, + uint256 profit_max_unlock_time + ) external returns (address); + + function vault_blueprint() external view returns (address); + + function api_version() external view returns (string memory); + + function protocol_fee_config() + external + view + returns (uint16 fee_bps, address fee_recipient); + + function set_protocol_fee_bps(uint16 new_protocol_fee_bps) external; + + function set_protocol_fee_recipient( + address new_protocol_fee_recipient + ) external; + + function set_custom_protocol_fee_bps( + address vault, + uint16 new_custom_protocol_fee + ) external; + + function remove_custom_protocol_fee(address vault) external; + + function shutdown_factory() external; + + function set_governance(address new_governance) external; + + function accept_governance() external; +} diff --git a/contracts/interfaces/VaultConstants.sol b/contracts/interfaces/VaultConstants.sol new file mode 100644 index 00000000..a2337b40 --- /dev/null +++ b/contracts/interfaces/VaultConstants.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.18; + +// prettier-ignore +contract Roles { + uint256 public constant ADD_STRATEGY_MANAGER = 1; + uint256 public constant REVOKE_STRATEGY_MANAGER = 2; + uint256 public constant FORCE_REVOKE_MANAGER = 4; + uint256 public constant ACCOUNTANT_MANAGER = 8; + uint256 public constant QUEUE_MANAGER = 16; + uint256 public constant REPORTING_MANAGER = 32; + uint256 public constant DEBT_MANAGER = 64; + uint256 public constant MAX_DEBT_MANAGER = 128; + uint256 public constant DEPOSIT_LIMIT_MANAGER = 256; + uint256 public constant WITHDRAW_LIMIT_MANAGER = 512; + uint256 public constant MINIMUM_IDLE_MANAGER = 1024; + uint256 public constant PROFIT_UNLOCK_MANAGER = 2048; + uint256 public constant DEBT_PURCHASER = 4096; + uint256 public constant EMERGENCY_MANAGER = 8192; + uint256 public constant ALL = 16383; +} + +// prettier-ignore +contract VaultConstants is Roles { + uint256 public constant MAX_QUEUE = 10; + uint256 public constant MAX_BPS = 10_000; + uint256 public constant MAX_BPS_EXTENDED = 1_000_000_000_000; + uint256 public constant STRATEGY_ADDED = 1; + uint256 public constant STRATEGY_REVOKED = 2; + uint256 public constant ROLE_OPENED = 1; + uint256 public constant ROLE_CLOSED = 2; +} diff --git a/contracts/test/ERC4626BaseStrategy.sol b/contracts/test/ERC4626BaseStrategy.sol index 58a59112..f550eac2 100644 --- a/contracts/test/ERC4626BaseStrategy.sol +++ b/contracts/test/ERC4626BaseStrategy.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IVault} from "../interfaces/IVault.sol"; -import {IStrategyERC4626} from "../interfaces/IStrategyERC4626.sol"; + import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -abstract contract ERC4626BaseStrategy is IStrategyERC4626, ERC4626 { +abstract contract ERC4626BaseStrategy is ERC4626 { using SafeERC20 for IERC20; - address public override vault; + address public vault; uint8 private _decimals; constructor( @@ -45,19 +45,11 @@ abstract contract ERC4626BaseStrategy is IStrategyERC4626, ERC4626 { // TODO: add roles (including vault) // TODO: should we force invest and freeFunds to be in deposit and withdraw functions? - function invest() external override { - // TODO: add permissioning ? - } + function invest() external virtual {} function freeFunds( uint256 _amount - ) external override returns (uint256 _freedFunds) { - // TODO: add permissioning ? - } - - function migrate(address _newStrategy) external virtual override { - // TODO: add permissioning - } + ) external virtual returns (uint256 _freedFunds) {} function _invest() internal virtual; @@ -65,14 +57,5 @@ abstract contract ERC4626BaseStrategy is IStrategyERC4626, ERC4626 { uint256 _amount ) internal virtual returns (uint256 amountFreed); - function _protectedTokens() - internal - view - virtual - returns (address[] memory); - - function sweep(address _token) external { - // TODO: add permissioning - // TODO: add logic - } + function sweep(address _token) external {} } diff --git a/contracts/test/Token.sol b/contracts/test/Token.sol index 11606c0e..0b41ef8b 100644 --- a/contracts/test/Token.sol +++ b/contracts/test/Token.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.14; +pragma solidity 0.8.18; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/contracts/test/mocks/ERC4626/BaseStrategyMock.sol b/contracts/test/mocks/ERC4626/BaseStrategyMock.sol index edfb83d7..2f95c54f 100644 --- a/contracts/test/mocks/ERC4626/BaseStrategyMock.sol +++ b/contracts/test/mocks/ERC4626/BaseStrategyMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; import {ERC4626BaseStrategy, IERC20} from "../../ERC4626BaseStrategy.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -47,14 +47,6 @@ abstract contract ERC4626BaseStrategyMock is ERC4626BaseStrategy { function delegatedAssets() external view - override returns (uint256 _delegatedAssets) {} - - function _protectedTokens() - internal - view - override - returns (address[] memory _protected) - {} } diff --git a/contracts/test/mocks/ERC4626/FaultyStrategy.sol b/contracts/test/mocks/ERC4626/FaultyStrategy.sol index 7f67072b..24d5a916 100644 --- a/contracts/test/mocks/ERC4626/FaultyStrategy.sol +++ b/contracts/test/mocks/ERC4626/FaultyStrategy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; import {ERC4626BaseStrategyMock, IERC20} from "./BaseStrategyMock.sol"; diff --git a/contracts/test/mocks/ERC4626/Generic4626.sol b/contracts/test/mocks/ERC4626/Generic4626.sol index 0f16b4f9..27c343a3 100644 --- a/contracts/test/mocks/ERC4626/Generic4626.sol +++ b/contracts/test/mocks/ERC4626/Generic4626.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/test/mocks/ERC4626/LiquidStrategy.sol b/contracts/test/mocks/ERC4626/LiquidStrategy.sol index 1fc5baad..5973a2f5 100644 --- a/contracts/test/mocks/ERC4626/LiquidStrategy.sol +++ b/contracts/test/mocks/ERC4626/LiquidStrategy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; import {ERC4626BaseStrategyMock, IERC20} from "./BaseStrategyMock.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -25,11 +25,4 @@ contract ERC4626LiquidStrategy is ERC4626BaseStrategyMock { ) public view override returns (uint256) { return _convertToAssets(balanceOf(_owner), Math.Rounding.Down); } - - function migrate(address _newStrategy) external override { - IERC20(asset()).safeTransfer( - _newStrategy, - IERC20(asset()).balanceOf(address(this)) - ); - } } diff --git a/contracts/test/mocks/ERC4626/LockedStrategy.sol b/contracts/test/mocks/ERC4626/LockedStrategy.sol index b5850f5d..d787b653 100644 --- a/contracts/test/mocks/ERC4626/LockedStrategy.sol +++ b/contracts/test/mocks/ERC4626/LockedStrategy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; import {ERC4626BaseStrategyMock, IERC20} from "./BaseStrategyMock.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -58,12 +58,4 @@ contract ERC4626LockedStrategy is ERC4626BaseStrategyMock { return convertToShares(balance); } } - - function migrate(address _newStrategy) external override { - require(lockedBalance == 0, "strat not liquid"); - IERC20(asset()).safeTransfer( - _newStrategy, - IERC20(asset()).balanceOf(address(this)) - ); - } } diff --git a/contracts/test/mocks/ERC4626/LossyStrategy.sol b/contracts/test/mocks/ERC4626/LossyStrategy.sol index 67523c1f..20a416f9 100644 --- a/contracts/test/mocks/ERC4626/LossyStrategy.sol +++ b/contracts/test/mocks/ERC4626/LossyStrategy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.14; +pragma solidity 0.8.18; import {ERC4626BaseStrategyMock, IERC20} from "./BaseStrategyMock.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -30,7 +30,11 @@ contract ERC4626LossyStrategy is ERC4626BaseStrategyMock { function totalAssets() public view override returns (uint256) { if (withdrawingLoss < 0) { - return uint256(int256(IERC20(asset()).balanceOf(address(this))) + withdrawingLoss); + return + uint256( + int256(IERC20(asset()).balanceOf(address(this))) + + withdrawingLoss + ); } else { return super.totalAssets(); } @@ -51,18 +55,12 @@ contract ERC4626LossyStrategy is ERC4626BaseStrategyMock { _burn(_owner, _shares); // Withdrawing loss simulates a loss while withdrawing IERC20(asset()).safeTransfer(_receiver, toWithdraw); - if(withdrawingLoss > 0) { + if (withdrawingLoss > 0) { // burns (to simulate loss while withdrawing) IERC20(asset()).safeTransfer(asset(), uint256(withdrawingLoss)); } - emit Withdraw( - _caller, - _receiver, - _owner, - toWithdraw, - _shares - ); + emit Withdraw(_caller, _receiver, _owner, toWithdraw, _shares); } function _freeFunds( @@ -74,13 +72,9 @@ contract ERC4626LossyStrategy is ERC4626BaseStrategyMock { } function maxRedeem(address) public view override returns (uint256) { - return convertToShares(IERC20(asset()).balanceOf(address(this)) - lockedFunds); - } - - function migrate(address _newStrategy) external override { - IERC20(asset()).safeTransfer( - _newStrategy, - IERC20(asset()).balanceOf(address(this)) - ); + return + convertToShares( + IERC20(asset()).balanceOf(address(this)) - lockedFunds + ); } } diff --git a/contracts/test/mocks/periphery/Accountant.vy b/contracts/test/mocks/periphery/Accountant.vy index 5112993a..40b18ca7 100644 --- a/contracts/test/mocks/periphery/Accountant.vy +++ b/contracts/test/mocks/periphery/Accountant.vy @@ -13,9 +13,6 @@ interface IVault: def strategies(strategy: address) -> StrategyParams: view def asset() -> address: view -interface IStrategy: - def delegatedAssets() -> uint256: view - # EVENTS # event CommitFeeManager: fee_manager: address @@ -80,7 +77,7 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 #management_fee total_fees: uint256 = ( - (strategy_params.current_debt - IStrategy(strategy).delegatedAssets()) + (strategy_params.current_debt) * duration * fee.management_fee / MAX_BPS @@ -95,7 +92,7 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 if total_fees > maximum_fee: return (maximum_fee, 0) else: - # Now taking loss from its own funds. In the future versions could be from different mecanisms + # Now taking loss from its own funds. In the future versions could be from different mechanisms asset_balance: uint256= ERC20(self.asset).balanceOf(self) refund_ratio: uint256 = self.refund_ratios[strategy] total_refunds = loss * refund_ratio / MAX_BPS diff --git a/contracts/test/mocks/periphery/FaultyAccountant.vy b/contracts/test/mocks/periphery/FaultyAccountant.vy index 87bebcf9..9734adb9 100644 --- a/contracts/test/mocks/periphery/FaultyAccountant.vy +++ b/contracts/test/mocks/periphery/FaultyAccountant.vy @@ -16,9 +16,6 @@ interface IVault: def asset() -> address: view def deposit(assets: uint256, receiver: address) -> uint256: nonpayable -interface IStrategy: - def delegatedAssets() -> uint256: view - # EVENTS # event CommitFeeManager: fee_manager: address @@ -90,7 +87,7 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 duration: uint256 = block.timestamp - strategy_params.last_report total_fees: uint256 = ( - (strategy_params.current_debt - IStrategy(strategy).delegatedAssets()) + (strategy_params.current_debt) * duration * fee.management_fee / MAX_BPS @@ -103,7 +100,7 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 total_fees += (gain * fee.performance_fee) / MAX_BPS total_refunds = min(asset_balance, gain * refund_ratio / MAX_BPS) else: - # Now taking loss from its own funds. In the future versions could be from different mecanisms + # Now taking loss from its own funds. In the future versions could be from different mechanisms total_refunds = min(asset_balance, loss * refund_ratio / MAX_BPS) return (total_fees, total_refunds) diff --git a/contracts/test/mocks/periphery/FlexibleAccountant.vy b/contracts/test/mocks/periphery/FlexibleAccountant.vy index 1598c0a9..1fc2b6cc 100644 --- a/contracts/test/mocks/periphery/FlexibleAccountant.vy +++ b/contracts/test/mocks/periphery/FlexibleAccountant.vy @@ -16,9 +16,6 @@ interface IVault: def asset() -> address: view def deposit(assets: uint256, receiver: address) -> uint256: nonpayable -interface IStrategy: - def delegatedAssets() -> uint256: view - # EVENTS # event CommitFeeManager: fee_manager: address @@ -90,7 +87,7 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 duration: uint256 = block.timestamp - strategy_params.last_report total_fees: uint256 = ( - (strategy_params.current_debt - IStrategy(strategy).delegatedAssets()) + (strategy_params.current_debt) * duration * fee.management_fee / MAX_BPS @@ -103,7 +100,7 @@ def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256 total_fees += (gain * fee.performance_fee) / MAX_BPS total_refunds = min(asset_balance, gain * refund_ratio / MAX_BPS) else: - # Now taking loss from its own funds. In the future versions could be from different mecanisms + # Now taking loss from its own funds. In the future versions could be from different mechanisms total_refunds = min(asset_balance, loss * refund_ratio / MAX_BPS) # accountant will deposit whatever it needs to avoid complex math in tests diff --git a/contracts/test/mocks/periphery/LimitModule.vy b/contracts/test/mocks/periphery/LimitModule.vy index a41fa1cb..285aa8d8 100644 --- a/contracts/test/mocks/periphery/LimitModule.vy +++ b/contracts/test/mocks/periphery/LimitModule.vy @@ -7,17 +7,17 @@ enforce_whitelist: public(bool) whitelist: public(HashMap[address, bool]) -default_deposit_limt: public(uint256) +default_deposit_limit: public(uint256) default_withdraw_limit: public(uint256) @external def __init__( - default_deposit_limt: uint256, + default_deposit_limit: uint256, default_withdraw_limit: uint256, enforce_whitelist: bool ): - self.default_deposit_limt = default_deposit_limt + self.default_deposit_limit = default_deposit_limit self.default_withdraw_limit = default_withdraw_limit self.enforce_whitelist = enforce_whitelist @@ -28,10 +28,10 @@ def available_deposit_limit(receiver: address) -> uint256: if not self.whitelist[receiver]: return 0 - if self.default_deposit_limt == MAX_UINT256: + if self.default_deposit_limit == MAX_UINT256: return MAX_UINT256 - return self.default_deposit_limt - IVault(msg.sender).totalAssets() + return self.default_deposit_limit - IVault(msg.sender).totalAssets() @view @external @@ -44,7 +44,7 @@ def set_whitelist(list: address): @external def set_default_deposit_limit(limit: uint256): - self.default_deposit_limt = limit + self.default_deposit_limit = limit @external def set_default_withdraw_limit(limit: uint256): diff --git a/package.json b/package.json index 1e77ee86..8ce40a8a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "prettier": "^2.6.0", "prettier-plugin-solidity": "^1.0.0-beta.19", "pretty-quick": "^3.1.3", - "solc": "^0.8.14", + "solc": "0.8.18", "solhint": "^3.3.7", "solhint-plugin-prettier": "^0.0.5", "solhint-plugin-yearn": "pandadefi/solhint-plugin-yearn" diff --git a/scripts/deploy.py b/scripts/deploy.py new file mode 100644 index 00000000..6daf1c9f --- /dev/null +++ b/scripts/deploy.py @@ -0,0 +1,95 @@ +from ape import project, accounts, Contract, chain, networks +from ape.utils import ZERO_ADDRESS +from web3 import Web3, HTTPProvider +from hexbytes import HexBytes +import os +import hashlib +from copy import deepcopy + +# Add the wallet to use here. +deployer = accounts.load("") + + +def deploy_blueprint_and_factory(): + print("Deploying Vault Factory on ChainID", chain.chain_id) + + if input("Do you want to continue? ") == "n": + return + + vault_factory = project.VaultFactory + vault = project.VaultV3 + deployer_contract = project.IDeployer.at( + "0x8D85e7c9A4e369E53Acc8d5426aE1568198b0112" + ) + salt_string = "v3.0.1" + + # Create a SHA-256 hash object + hash_object = hashlib.sha256() + # Update the hash object with the string data + hash_object.update(salt_string.encode("utf-8")) + # Get the hexadecimal representation of the hash + hex_hash = hash_object.hexdigest() + # Convert the hexadecimal hash to an integer + salt = int(hex_hash, 16) + + print(f"Salt we are using {salt}") + print("Init balance:", deployer.balance / 1e18) + + # generate and deploy blueprint + vault_copy = deepcopy(vault) + blueprint_bytecode = b"\xFE\x71\x00" + HexBytes( + vault_copy.contract_type.deployment_bytecode.bytecode + ) + len_bytes = len(blueprint_bytecode).to_bytes(2, "big") + blueprint_constructor = vault_copy.constructor.encode_input( + ZERO_ADDRESS, "", "", ZERO_ADDRESS, 0 + ) + + # ERC5202 + blueprint_deploy_bytecode = HexBytes( + b"\x61" + + len_bytes + + b"\x3d\x81\x60\x0a\x3d\x39\xf3" + + blueprint_bytecode + + blueprint_constructor + ) + + print(f"Deploying BluePrint...") + + blueprint_tx = deployer_contract.deploy( + blueprint_deploy_bytecode, salt, sender=deployer + ) + + blueprint_event = list(blueprint_tx.decode_logs(deployer_contract.Deployed)) + + blueprint_address = blueprint_event[0].addr + + print(f"Deployed the vault Blueprint to {blueprint_address}") + + # deploy factory + print(f"Deploying factory...") + + factory_constructor = vault_factory.constructor.encode_input( + "Yearn v3.0.1 Vault Factory", + blueprint_address, + "0x33333333D5eFb92f19a5F94a43456b3cec2797AE", + ) + + factory_deploy_bytecode = HexBytes( + HexBytes(vault_factory.contract_type.deployment_bytecode.bytecode) + + factory_constructor + ) + + factory_tx = deployer_contract.deploy( + factory_deploy_bytecode, salt, sender=deployer + ) + + factory_event = list(factory_tx.decode_logs(deployer_contract.Deployed)) + + factory_address = factory_event[0].addr + + print(f"Deployed Vault Factory to {factory_address}") + + +def main(): + deploy_blueprint_and_factory() diff --git a/tests/utils/constants.py b/tests/utils/constants.py index fa98717b..862d8c4b 100644 --- a/tests/utils/constants.py +++ b/tests/utils/constants.py @@ -24,6 +24,7 @@ class ROLES(IntFlag): PROFIT_UNLOCK_MANAGER = 2048 DEBT_PURCHASER = 4096 EMERGENCY_MANAGER = 8192 + ALL = 16383 class StrategyChangeType(IntFlag): diff --git a/yarn.lock b/yarn.lock index 1bc4763b..bd02e075 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2404,10 +2404,10 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" -solc@^0.8.14: - version "0.8.19" - resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.19.tgz#cac6541106ae3cff101c740042c7742aa56a2ed3" - integrity sha512-yqurS3wzC4LdEvmMobODXqprV4MYJcVtinuxgrp61ac8K2zz40vXA0eSAskSHPgv8dQo7Nux39i3QBsHx4pqyA== +solc@0.8.18: + version "0.8.18" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.18.tgz#a05ce8918540eda5f10aa91f0f52f239b9645dad" + integrity sha512-wVAa2Y3BYd64Aby5LsgS3g6YC2NvZ3bJ+A8TAIAukfVuQb3AjyGrLZpyxQk5YLn14G35uZtSnIgHEpab9klOLQ== dependencies: command-exists "^1.2.8" commander "^8.1.0"