diff --git a/contracts/README.md b/contracts/README.md index 8403927e9f..1e88700f01 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -125,11 +125,10 @@ You can enable the "hot deploy" mode when doing fork testing development. The mo To enable Hot Deploys set the HOT_DEPLOY variable in the contracts/.env file. Enable various modes using comma separated flags to direct which contracts need source updated (in the node runtime): - strategy -> strategy contract associated to fixture -- vaultCore -> vaultCore or oethVaultCore depending on the nature of the fixture -- vaultAdmin -> vaultAdmin or oethVaultAdmin depending on the nature of the fixture +- vault -> OUSDVault or OETHVault depending on the nature of the fixture - harvester -> harvester or oethHarvester (not yet supported) -example: HOT_DEPLOY=strategy,vaultCore,vaultAdmin,harvester +example: HOT_DEPLOY=strategy,vault,harvester #### Supporting new fixtures / contracts diff --git a/contracts/contracts/harvest/AbstractHarvester.sol b/contracts/contracts/harvest/AbstractHarvester.sol index 99471bdad7..287bbc03e2 100644 --- a/contracts/contracts/harvest/AbstractHarvester.sol +++ b/contracts/contracts/harvest/AbstractHarvester.sol @@ -198,7 +198,6 @@ abstract contract AbstractHarvester is Governable { // Revert if feed does not exist // slither-disable-next-line unused-return - IOracle(IVault(vaultAddress).priceProvider()).price(_tokenAddress); IERC20 token = IERC20(_tokenAddress); // if changing token swap provider cancel existing allowance @@ -443,10 +442,11 @@ abstract contract AbstractHarvester is Governable { _harvest(_strategyAddr); IStrategy strategy = IStrategy(_strategyAddr); address[] memory rewardTokens = strategy.getRewardTokenAddresses(); - IOracle priceProvider = IOracle(IVault(vaultAddress).priceProvider()); uint256 len = rewardTokens.length; for (uint256 i = 0; i < len; ++i) { - _swap(rewardTokens[i], _rewardTo, priceProvider); + // This harvester contract is not used anymore. Keeping the code + // for passing test deployment. Safe to use address(0x1) as oracle. + _swap(rewardTokens[i], _rewardTo, IOracle(address(0x1))); } } diff --git a/contracts/contracts/interfaces/IVault.sol b/contracts/contracts/interfaces/IVault.sol index 0b0bc98e93..c517cf82b0 100644 --- a/contracts/contracts/interfaces/IVault.sol +++ b/contracts/contracts/interfaces/IVault.sol @@ -6,8 +6,6 @@ import { VaultStorage } from "../vault/VaultStorage.sol"; interface IVault { // slither-disable-start constable-states - event AssetSupported(address _asset); - event AssetDefaultStrategyUpdated(address _asset, address _strategy); event AssetAllocated(address _asset, address _strategy, uint256 _amount); event StrategyApproved(address _addr); event StrategyRemoved(address _addr); @@ -15,11 +13,10 @@ interface IVault { event Redeem(address _addr, uint256 _value); event CapitalPaused(); event CapitalUnpaused(); + event DefaultStrategyUpdated(address _strategy); event RebasePaused(); event RebaseUnpaused(); event VaultBufferUpdated(uint256 _vaultBuffer); - event RedeemFeeUpdated(uint256 _redeemFeeBps); - event PriceProviderUpdated(address _priceProvider); event AllocateThresholdUpdated(uint256 _threshold); event RebaseThresholdUpdated(uint256 _threshold); event StrategistUpdated(address _address); @@ -27,18 +24,10 @@ interface IVault { event YieldDistribution(address _to, uint256 _yield, uint256 _fee); event TrusteeFeeBpsChanged(uint256 _basis); event TrusteeAddressChanged(address _address); - event SwapperChanged(address _address); - event SwapAllowedUndervalueChanged(uint256 _basis); - event SwapSlippageChanged(address _asset, uint256 _basis); - event Swapped( - address indexed _fromAsset, - address indexed _toAsset, - uint256 _fromAssetAmount, - uint256 _toAssetAmount - ); event StrategyAddedToMintWhitelist(address indexed strategy); event StrategyRemovedFromMintWhitelist(address indexed strategy); - event DripperChanged(address indexed _dripper); + event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond); + event DripDurationChanged(uint256 dripDuration); event WithdrawalRequested( address indexed _withdrawer, uint256 indexed _requestId, @@ -51,6 +40,7 @@ interface IVault { uint256 _amount ); event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable); + event WithdrawalClaimDelayUpdated(uint256 _newDelay); // Governable.sol function transferGovernance(address _newGovernor) external; @@ -59,17 +49,7 @@ interface IVault { function governor() external view returns (address); - function ADMIN_IMPLEMENTATION() external view returns (address); - // VaultAdmin.sol - function setPriceProvider(address _priceProvider) external; - - function priceProvider() external view returns (address); - - function setRedeemFeeBps(uint256 _redeemFeeBps) external; - - function redeemFeeBps() external view returns (uint256); - function setVaultBuffer(uint256 _vaultBuffer) external; function vaultBuffer() external view returns (uint256); @@ -98,28 +78,13 @@ interface IVault { function trusteeFeeBps() external view returns (uint256); - function ousdMetaStrategy() external view returns (address); - - function setSwapper(address _swapperAddr) external; - - function setSwapAllowedUndervalue(uint16 _percentageBps) external; - - function setOracleSlippage(address _asset, uint16 _allowedOracleSlippageBps) - external; - - function supportAsset(address _asset, uint8 _unitConversion) external; - function approveStrategy(address _addr) external; function removeStrategy(address _addr) external; - function setAssetDefaultStrategy(address _asset, address _strategy) - external; + function setDefaultStrategy(address _strategy) external; - function assetDefaultStrategies(address _asset) - external - view - returns (address); + function defaultStrategy() external view returns (address); function pauseRebase() external; @@ -135,10 +100,6 @@ interface IVault { function transferToken(address _asset, uint256 _amount) external; - function priceUnitMint(address asset) external view returns (uint256); - - function priceUnitRedeem(address asset) external view returns (uint256); - function withdrawAllFromStrategy(address _strategyAddr) external; function withdrawAllFromStrategies() external; @@ -172,67 +133,38 @@ interface IVault { function rebase() external; - function swapCollateral( - address fromAsset, - address toAsset, - uint256 fromAssetAmount, - uint256 minToAssetAmount, - bytes calldata data - ) external returns (uint256 toAssetAmount); - function totalValue() external view returns (uint256 value); function checkBalance(address _asset) external view returns (uint256); + /// @notice Deprecated: use calculateRedeemOutput function calculateRedeemOutputs(uint256 _amount) external view returns (uint256[] memory); - function getAssetCount() external view returns (uint256); - - function getAssetConfig(address _asset) + function calculateRedeemOutput(uint256 _amount) external view - returns (VaultStorage.Asset memory config); + returns (uint256); + + function getAssetCount() external view returns (uint256); function getAllAssets() external view returns (address[] memory); function getStrategyCount() external view returns (uint256); - function swapper() external view returns (address); - - function allowedSwapUndervalue() external view returns (uint256); - function getAllStrategies() external view returns (address[] memory); + /// @notice Deprecated. function isSupportedAsset(address _asset) external view returns (bool); - function netOusdMintForStrategyThreshold() external view returns (uint256); - - function setOusdMetaStrategy(address _ousdMetaStrategy) external; - - function setNetOusdMintForStrategyThreshold(uint256 _threshold) external; - - function netOusdMintedForStrategy() external view returns (int256); - - function setDripper(address _dripper) external; - function dripper() external view returns (address); - function weth() external view returns (address); - - function cacheWETHAssetIndex() external; - - function wethAssetIndex() external view returns (uint256); + function asset() external view returns (address); - function initialize(address, address) external; + function initialize(address) external; - function setAdminImpl(address) external; - - function removeAsset(address _asset) external; - - // These are OETH specific functions function addWithdrawalQueueLiquidity() external; function requestWithdrawal(uint256 _amount) @@ -257,7 +189,6 @@ interface IVault { view returns (VaultStorage.WithdrawalRequest memory); - // OETHb specific functions function addStrategyToMintWhitelist(address strategyAddr) external; function removeStrategyFromMintWhitelist(address strategyAddr) external; @@ -285,5 +216,7 @@ interface IVault { function previewYield() external view returns (uint256 yield); + function weth() external view returns (address); + // slither-disable-end constable-states } diff --git a/contracts/contracts/mocks/MockEvilReentrantContract.sol b/contracts/contracts/mocks/MockEvilReentrantContract.sol index 47fee94c19..fea2c81bb1 100644 --- a/contracts/contracts/mocks/MockEvilReentrantContract.sol +++ b/contracts/contracts/mocks/MockEvilReentrantContract.sol @@ -19,6 +19,7 @@ contract MockEvilReentrantContract { IVault public immutable oethVault; address public immutable poolAddress; bytes32 public immutable balancerPoolId; + address public immutable priceProvider; constructor( address _balancerVault, @@ -37,7 +38,6 @@ contract MockEvilReentrantContract { } function doEvilStuff() public { - address priceProvider = oethVault.priceProvider(); uint256 rethPrice = IOracle(priceProvider).price(address(reth)); // 1. Join pool @@ -99,9 +99,6 @@ contract MockEvilReentrantContract { virtual returns (uint256 bptExpected) { - // Get the oracle from the OETH Vault - address priceProvider = oethVault.priceProvider(); - for (uint256 i = 0; i < _assets.length; ++i) { uint256 strategyAssetMarketPrice = IOracle(priceProvider).price( _assets[i] diff --git a/contracts/contracts/mocks/MockNonRebasing.sol b/contracts/contracts/mocks/MockNonRebasing.sol index 61bbe79c6e..b3ad870347 100644 --- a/contracts/contracts/mocks/MockNonRebasing.sol +++ b/contracts/contracts/mocks/MockNonRebasing.sol @@ -47,7 +47,7 @@ contract MockNonRebasing { } function redeemOusd(address _vaultContract, uint256 _amount) public { - IVault(_vaultContract).redeem(_amount, 0); + IVault(_vaultContract).requestWithdrawal(_amount); } function approveFor( diff --git a/contracts/contracts/mocks/MockOETHVault.sol b/contracts/contracts/mocks/MockOETHVault.sol index 9e34d8430f..7b72c90189 100644 --- a/contracts/contracts/mocks/MockOETHVault.sol +++ b/contracts/contracts/mocks/MockOETHVault.sol @@ -1,25 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { OETHVaultCore } from "../vault/OETHVaultCore.sol"; +import { OETHVault } from "../vault/OETHVault.sol"; import { StableMath } from "../utils/StableMath.sol"; import "../utils/Helpers.sol"; -contract MockOETHVault is OETHVaultCore { +contract MockOETHVault is OETHVault { using StableMath for uint256; - constructor(address _weth) OETHVaultCore(_weth) { + constructor(address _weth) OETHVault(_weth) { _setGovernor(msg.sender); } function supportAsset(address asset) external { - assets[asset] = Asset({ - isSupported: true, - unitConversion: UnitConversion(0), - decimals: 18, - allowedOracleSlippageBps: 0 - }); - - allAssets.push(asset); + require(asset == asset, "Only asset supported"); } } diff --git a/contracts/contracts/mocks/MockOETHVaultAdmin.sol b/contracts/contracts/mocks/MockOETHVaultAdmin.sol index a2664212b5..43c9689bd8 100644 --- a/contracts/contracts/mocks/MockOETHVaultAdmin.sol +++ b/contracts/contracts/mocks/MockOETHVaultAdmin.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { OETHVaultAdmin } from "../vault/OETHVaultAdmin.sol"; +import { OETHVault } from "../vault/OETHVault.sol"; -contract MockOETHVaultAdmin is OETHVaultAdmin { - constructor(address _weth) OETHVaultAdmin(_weth) {} +contract MockOETHVault is OETHVault { + constructor(address _weth) OETHVault(_weth) {} // fetches the WETH amount in outstanding withdrawals function outstandingWithdrawalsAmount() @@ -19,6 +19,6 @@ contract MockOETHVaultAdmin is OETHVaultAdmin { } function wethAvailable() external view returns (uint256) { - return _wethAvailable(); + return _assetAvailable(); } } diff --git a/contracts/contracts/mocks/MockRebornMinter.sol b/contracts/contracts/mocks/MockRebornMinter.sol index b0c812ca56..aa7394b7d1 100644 --- a/contracts/contracts/mocks/MockRebornMinter.sol +++ b/contracts/contracts/mocks/MockRebornMinter.sol @@ -97,8 +97,8 @@ contract Reborner { log("We are attempting to mint.."); address asset = sanctum.asset(); address vault = sanctum.vault(); - IERC20(asset).approve(vault, 1e18); - IVault(vault).mint(asset, 1e18, 0); + IERC20(asset).approve(vault, 1e6); + IVault(vault).mint(asset, 1e6, 0); log("We are now minting.."); } diff --git a/contracts/contracts/mocks/MockVault.sol b/contracts/contracts/mocks/MockVault.sol index 717c1f31f3..fb084b6883 100644 --- a/contracts/contracts/mocks/MockVault.sol +++ b/contracts/contracts/mocks/MockVault.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { VaultCore } from "../vault/VaultCore.sol"; +import { VaultAdmin } from "../vault/VaultAdmin.sol"; import { StableMath } from "../utils/StableMath.sol"; -import { VaultInitializer } from "../vault/VaultInitializer.sol"; import "../utils/Helpers.sol"; -contract MockVault is VaultCore { +contract MockVault is VaultAdmin { using StableMath for uint256; uint256 storedTotalValue; + constructor(address _asset) VaultAdmin(_asset) {} + function setTotalValue(uint256 _value) public { storedTotalValue = _value; } @@ -31,15 +32,11 @@ contract MockVault is VaultCore { { // Avoids rounding errors by returning the total value // in a single currency - if (allAssets[0] == _asset) { + if (asset == _asset) { uint256 decimals = Helpers.getDecimals(_asset); return storedTotalValue.scaleBy(decimals, 18); } else { return 0; } } - - function setMaxSupplyDiff(uint256 _maxSupplyDiff) external onlyGovernor { - maxSupplyDiff = _maxSupplyDiff; - } } diff --git a/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol b/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol index 7043497725..e10e66670b 100644 --- a/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol +++ b/contracts/contracts/mocks/MockVaultCoreInstantRebase.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import { VaultCore } from "../vault/VaultCore.sol"; contract MockVaultCoreInstantRebase is VaultCore { + constructor(address _asset) VaultCore(_asset) {} + function _nextYield(uint256 supply, uint256 vaultValue) internal view diff --git a/contracts/contracts/strategies/BridgedWOETHStrategy.sol b/contracts/contracts/strategies/BridgedWOETHStrategy.sol index 1f602318d0..a5ea8b9aa7 100644 --- a/contracts/contracts/strategies/BridgedWOETHStrategy.sol +++ b/contracts/contracts/strategies/BridgedWOETHStrategy.sol @@ -21,6 +21,7 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy { IWETH9 public immutable weth; IERC20 public immutable bridgedWOETH; IERC20 public immutable oethb; + IOracle public immutable oracle; uint256 public constant MAX_PRICE_STALENESS = 2 days; @@ -31,11 +32,13 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy { BaseStrategyConfig memory _stratConfig, address _weth, address _bridgedWOETH, - address _oethb + address _oethb, + address _oracle ) InitializableAbstractStrategy(_stratConfig) { weth = IWETH9(_weth); bridgedWOETH = IERC20(_bridgedWOETH); oethb = IERC20(_oethb); + oracle = IOracle(_oracle); } function initialize(uint128 _maxPriceDiffBps) @@ -100,8 +103,7 @@ contract BridgedWOETHStrategy is InitializableAbstractStrategy { */ function _updateWOETHOraclePrice() internal returns (uint256) { // WETH price per unit of bridged wOETH - uint256 oraclePrice = IOracle(IVault(vaultAddress).priceProvider()) - .price(address(bridgedWOETH)); + uint256 oraclePrice = oracle.price(address(bridgedWOETH)); // 1 wOETH > 1 WETH, always require(oraclePrice > 1 ether, "Invalid wOETH value"); diff --git a/contracts/contracts/vault/OETHBaseVault.sol b/contracts/contracts/vault/OETHBaseVault.sol new file mode 100644 index 0000000000..16eebe01f5 --- /dev/null +++ b/contracts/contracts/vault/OETHBaseVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title OETH Base VaultAdmin Contract + * @author Origin Protocol Inc + */ +contract OETHBaseVault is VaultAdmin { + constructor(address _weth) VaultAdmin(_weth) {} +} diff --git a/contracts/contracts/vault/OETHBaseVaultAdmin.sol b/contracts/contracts/vault/OETHBaseVaultAdmin.sol deleted file mode 100644 index d29d863ac3..0000000000 --- a/contracts/contracts/vault/OETHBaseVaultAdmin.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultAdmin } from "./OETHVaultAdmin.sol"; - -/** - * @title OETH Base VaultAdmin Contract - * @author Origin Protocol Inc - */ -contract OETHBaseVaultAdmin is OETHVaultAdmin { - constructor(address _weth) OETHVaultAdmin(_weth) {} -} diff --git a/contracts/contracts/vault/OETHBaseVaultCore.sol b/contracts/contracts/vault/OETHBaseVaultCore.sol deleted file mode 100644 index cfa0b6a6f7..0000000000 --- a/contracts/contracts/vault/OETHBaseVaultCore.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { StableMath } from "../utils/StableMath.sol"; -import { OETHVaultCore } from "./OETHVaultCore.sol"; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IStrategy } from "../interfaces/IStrategy.sol"; - -/** - * @title OETH Base VaultCore Contract - * @author Origin Protocol Inc - */ -contract OETHBaseVaultCore is OETHVaultCore { - using SafeERC20 for IERC20; - using StableMath for uint256; - - constructor(address _weth) OETHVaultCore(_weth) {} - - // @inheritdoc OETHVaultCore - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal - virtual - override - { - // Only Strategist or Governor can redeem using the Vault for now. - // We don't have the onlyGovernorOrStrategist modifier on VaultCore. - // Since we won't be using that modifier anywhere in the VaultCore as well, - // the check has been added inline instead of moving it to VaultStorage. - require( - msg.sender == strategistAddr || isGovernor(), - "Caller is not the Strategist or Governor" - ); - - super._redeem(_amount, _minimumUnitAmount); - } -} diff --git a/contracts/contracts/vault/OETHPlumeVault.sol b/contracts/contracts/vault/OETHPlumeVault.sol new file mode 100644 index 0000000000..5a4e8e81a8 --- /dev/null +++ b/contracts/contracts/vault/OETHPlumeVault.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title OETH Plume VaultAdmin Contract + * @author Origin Protocol Inc + */ +contract OETHPlumeVault is VaultAdmin { + constructor(address _weth) VaultAdmin(_weth) {} + + // @inheritdoc VaultAdmin + function _mint( + address, + uint256 _amount, + uint256 + ) internal virtual { + // Only Strategist or Governor can mint using the Vault for now. + // This allows the strateigst to fund the Vault with WETH when + // removing liquidi from wOETH strategy. + require( + msg.sender == strategistAddr || isGovernor(), + "Caller is not the Strategist or Governor" + ); + + super._mint(_amount); + } +} diff --git a/contracts/contracts/vault/OETHPlumeVaultCore.sol b/contracts/contracts/vault/OETHPlumeVaultCore.sol deleted file mode 100644 index 28404a4c84..0000000000 --- a/contracts/contracts/vault/OETHPlumeVaultCore.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultCore } from "./OETHVaultCore.sol"; - -/** - * @title OETH Plume VaultCore Contract - * @author Origin Protocol Inc - */ -contract OETHPlumeVaultCore is OETHVaultCore { - constructor(address _weth) OETHVaultCore(_weth) {} - - // @inheritdoc OETHVaultCore - function _mint( - address _asset, - uint256 _amount, - uint256 _minimumOusdAmount - ) internal virtual override { - // Only Strategist or Governor can mint using the Vault for now. - // This allows the strateigst to fund the Vault with WETH when - // removing liquidi from wOETH strategy. - require( - msg.sender == strategistAddr || isGovernor(), - "Caller is not the Strategist or Governor" - ); - - super._mint(_asset, _amount, _minimumOusdAmount); - } - - // @inheritdoc OETHVaultCore - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal - virtual - override - { - // Only Strategist or Governor can redeem using the Vault for now. - // We don't have the onlyGovernorOrStrategist modifier on VaultCore. - // Since we won't be using that modifier anywhere in the VaultCore as well, - // the check has been added inline instead of moving it to VaultStorage. - require( - msg.sender == strategistAddr || isGovernor(), - "Caller is not the Strategist or Governor" - ); - - super._redeem(_amount, _minimumUnitAmount); - } -} diff --git a/contracts/contracts/vault/OETHVault.sol b/contracts/contracts/vault/OETHVault.sol index 0ec1690ad5..eb7d3b9f55 100644 --- a/contracts/contracts/vault/OETHVault.sol +++ b/contracts/contracts/vault/OETHVault.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import { Vault } from "./Vault.sol"; +import { VaultAdmin } from "./VaultAdmin.sol"; /** - * @title OETH Vault Contract + * @title OETH VaultAdmin Contract * @author Origin Protocol Inc */ -contract OETHVault is Vault { - +contract OETHVault is VaultAdmin { + constructor(address _weth) VaultAdmin(_weth) {} } diff --git a/contracts/contracts/vault/OETHVaultAdmin.sol b/contracts/contracts/vault/OETHVaultAdmin.sol deleted file mode 100644 index 080dec140b..0000000000 --- a/contracts/contracts/vault/OETHVaultAdmin.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { IStrategy } from "../interfaces/IStrategy.sol"; -import { IVault } from "../interfaces/IVault.sol"; -import { VaultAdmin } from "./VaultAdmin.sol"; - -/** - * @title OETH VaultAdmin Contract - * @author Origin Protocol Inc - */ -contract OETHVaultAdmin is VaultAdmin { - using SafeERC20 for IERC20; - - address public immutable weth; - - constructor(address _weth) { - weth = _weth; - } - - /** - * @notice Adds a strategy to the mint whitelist. - * Reverts if strategy isn't approved on Vault. - * @param strategyAddr Strategy address - */ - function addStrategyToMintWhitelist(address strategyAddr) - external - onlyGovernor - { - require(strategies[strategyAddr].isSupported, "Strategy not approved"); - - require( - !isMintWhitelistedStrategy[strategyAddr], - "Already whitelisted" - ); - - isMintWhitelistedStrategy[strategyAddr] = true; - - emit StrategyAddedToMintWhitelist(strategyAddr); - } - - /** - * @notice Removes a strategy from the mint whitelist. - * @param strategyAddr Strategy address - */ - function removeStrategyFromMintWhitelist(address strategyAddr) - external - onlyGovernor - { - // Intentionally skipping `strategies.isSupported` check since - // we may wanna remove an address even after removing the strategy - - require(isMintWhitelistedStrategy[strategyAddr], "Not whitelisted"); - - isMintWhitelistedStrategy[strategyAddr] = false; - - emit StrategyRemovedFromMintWhitelist(strategyAddr); - } - - /// @dev Simplified version of the deposit function as WETH is the only supported asset. - function _depositToStrategy( - address _strategyToAddress, - address[] calldata _assets, - uint256[] calldata _amounts - ) internal override { - require( - strategies[_strategyToAddress].isSupported, - "Invalid to Strategy" - ); - require( - _assets.length == 1 && _amounts.length == 1 && _assets[0] == weth, - "Only WETH is supported" - ); - - // Check the there is enough WETH to transfer once the WETH reserved for the withdrawal queue is accounted for - require(_amounts[0] <= _wethAvailable(), "Not enough WETH available"); - - // Send required amount of funds to the strategy - IERC20(weth).safeTransfer(_strategyToAddress, _amounts[0]); - - // Deposit all the funds that have been sent to the strategy - IStrategy(_strategyToAddress).depositAll(); - } - - function _withdrawFromStrategy( - address _recipient, - address _strategyFromAddress, - address[] calldata _assets, - uint256[] calldata _amounts - ) internal override { - super._withdrawFromStrategy( - _recipient, - _strategyFromAddress, - _assets, - _amounts - ); - - IVault(address(this)).addWithdrawalQueueLiquidity(); - } - - function _withdrawAllFromStrategy(address _strategyAddr) internal override { - super._withdrawAllFromStrategy(_strategyAddr); - - IVault(address(this)).addWithdrawalQueueLiquidity(); - } - - function _withdrawAllFromStrategies() internal override { - super._withdrawAllFromStrategies(); - - IVault(address(this)).addWithdrawalQueueLiquidity(); - } - - /// @dev Calculate how much WETH in the vault is not reserved for the withdrawal queue. - // That is, it is available to be redeemed or deposited into a strategy. - function _wethAvailable() internal view returns (uint256 wethAvailable) { - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // The amount of WETH that is still to be claimed in the withdrawal queue - uint256 outstandingWithdrawals = queue.queued - queue.claimed; - - // The amount of sitting in WETH in the vault - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - - // If there is not enough WETH in the vault to cover the outstanding withdrawals - if (wethBalance <= outstandingWithdrawals) { - return 0; - } - - return wethBalance - outstandingWithdrawals; - } - - function _swapCollateral( - address, - address, - uint256, - uint256, - bytes calldata - ) internal pure override returns (uint256) { - revert("Collateral swap not supported"); - } -} diff --git a/contracts/contracts/vault/OETHVaultCore.sol b/contracts/contracts/vault/OETHVaultCore.sol deleted file mode 100644 index cbc59a8eee..0000000000 --- a/contracts/contracts/vault/OETHVaultCore.sol +++ /dev/null @@ -1,524 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import { StableMath } from "../utils/StableMath.sol"; -import { VaultCore } from "./VaultCore.sol"; -import { IStrategy } from "../interfaces/IStrategy.sol"; - -/** - * @title OETH VaultCore Contract - * @author Origin Protocol Inc - */ -contract OETHVaultCore is VaultCore { - using SafeERC20 for IERC20; - using StableMath for uint256; - - address public immutable weth; - uint256 public wethAssetIndex; - - // For future use (because OETHBaseVaultCore inherits from this) - uint256[50] private __gap; - - constructor(address _weth) { - weth = _weth; - } - - /** - * @dev Caches WETH's index in `allAssets` variable. - * Reduces gas usage by redeem by caching that. - */ - function cacheWETHAssetIndex() external onlyGovernor { - uint256 assetCount = allAssets.length; - for (uint256 i; i < assetCount; ++i) { - if (allAssets[i] == weth) { - wethAssetIndex = i; - break; - } - } - - require(allAssets[wethAssetIndex] == weth, "Invalid WETH Asset Index"); - } - - // @inheritdoc VaultCore - function mintForStrategy(uint256 amount) - external - override - whenNotCapitalPaused - { - require( - strategies[msg.sender].isSupported == true, - "Unsupported strategy" - ); - require( - isMintWhitelistedStrategy[msg.sender] == true, - "Not whitelisted strategy" - ); - - emit Mint(msg.sender, amount); - - // Mint matching amount of OTokens - oUSD.mint(msg.sender, amount); - } - - // @inheritdoc VaultCore - function burnForStrategy(uint256 amount) - external - override - whenNotCapitalPaused - { - require( - strategies[msg.sender].isSupported == true, - "Unsupported strategy" - ); - require( - isMintWhitelistedStrategy[msg.sender] == true, - "Not whitelisted strategy" - ); - - emit Redeem(msg.sender, amount); - - // Burn OTokens - oUSD.burn(msg.sender, amount); - } - - // @inheritdoc VaultCore - // slither-disable-start reentrancy-no-eth - function _mint( - address _asset, - uint256 _amount, - uint256 _minimumOusdAmount - ) internal virtual override { - require(_asset == weth, "Unsupported asset for minting"); - require(_amount > 0, "Amount must be greater than 0"); - require( - _amount >= _minimumOusdAmount, - "Mint amount lower than minimum" - ); - - emit Mint(msg.sender, _amount); - - // Rebase must happen before any transfers occur. - if (!rebasePaused && _amount >= rebaseThreshold) { - _rebase(); - } - - // Mint oTokens - oUSD.mint(msg.sender, _amount); - - // Transfer the deposited coins to the vault - IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - - // Give priority to the withdrawal queue for the new WETH liquidity - _addWithdrawalQueueLiquidity(); - - // Auto-allocate if necessary - if (_amount >= autoAllocateThreshold) { - _allocate(); - } - } - - // slither-disable-end reentrancy-no-eth - - // @inheritdoc VaultCore - function _calculateRedeemOutputs(uint256 _amount) - internal - view - virtual - override - returns (uint256[] memory outputs) - { - // Overrides `VaultCore._calculateRedeemOutputs` to redeem with only - // WETH instead of LST-mix. Doesn't change the function signature - // for backward compatibility - - // Calculate redeem fee - if (redeemFeeBps > 0) { - uint256 redeemFee = _amount.mulTruncateScale(redeemFeeBps, 1e4); - _amount = _amount - redeemFee; - } - - // Ensure that the WETH index is cached - uint256 _wethAssetIndex = wethAssetIndex; - require( - allAssets[_wethAssetIndex] == weth, - "WETH Asset index not cached" - ); - - outputs = new uint256[](allAssets.length); - outputs[_wethAssetIndex] = _amount; - } - - // @inheritdoc VaultCore - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal - virtual - override - { - // Override `VaultCore._redeem` to simplify it. Gets rid of oracle - // usage and looping through all assets for LST-mix redeem. Instead - // does a simple WETH-only redeem. - emit Redeem(msg.sender, _amount); - - if (_amount == 0) { - return; - } - - // Amount excluding fees - // No fee for the strategist or the governor, makes it easier to do operations - uint256 amountMinusFee = (msg.sender == strategistAddr || isGovernor()) - ? _amount - : _calculateRedeemOutputs(_amount)[wethAssetIndex]; - - require( - amountMinusFee >= _minimumUnitAmount, - "Redeem amount lower than minimum" - ); - - // Is there enough WETH in the Vault available after accounting for the withdrawal queue - require(_wethAvailable() >= amountMinusFee, "Liquidity error"); - - // Transfer WETH minus the fee to the redeemer - IERC20(weth).safeTransfer(msg.sender, amountMinusFee); - - // Burn OETH from user (including fees) - oUSD.burn(msg.sender, _amount); - - // Prevent insolvency - _postRedeem(_amount); - } - - /** - * @notice Request an asynchronous withdrawal of WETH in exchange for OETH. - * The OETH is burned on request and the WETH is transferred to the withdrawer on claim. - * This request can be claimed once the withdrawal queue's `claimable` amount - * is greater than or equal this request's `queued` amount. - * There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs - * enough WETH liquidity in the Vault to satisfy all the outstanding requests to that point in the queue. - * OETH is converted to WETH at 1:1. - * @param _amount Amount of OETH to burn. - * @return requestId Unique ID for the withdrawal request - * @return queued Cumulative total of all WETH queued including already claimed requests. - */ - function requestWithdrawal(uint256 _amount) - external - virtual - whenNotCapitalPaused - nonReentrant - returns (uint256 requestId, uint256 queued) - { - require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); - - // The check that the requester has enough OETH is done in to later burn call - - requestId = withdrawalQueueMetadata.nextWithdrawalIndex; - queued = withdrawalQueueMetadata.queued + _amount; - - // Store the next withdrawal request - withdrawalQueueMetadata.nextWithdrawalIndex = SafeCast.toUint128( - requestId + 1 - ); - // Store the updated queued amount which reserves WETH in the withdrawal queue - // and reduces the vault's total assets - withdrawalQueueMetadata.queued = SafeCast.toUint128(queued); - // Store the user's withdrawal request - withdrawalRequests[requestId] = WithdrawalRequest({ - withdrawer: msg.sender, - claimed: false, - timestamp: uint40(block.timestamp), - amount: SafeCast.toUint128(_amount), - queued: SafeCast.toUint128(queued) - }); - - // Burn the user's OETH - oUSD.burn(msg.sender, _amount); - - // Prevent withdrawal if the vault is solvent by more than the allowed percentage - _postRedeem(_amount); - - emit WithdrawalRequested(msg.sender, requestId, _amount, queued); - } - - // slither-disable-start reentrancy-no-eth - /** - * @notice Claim a previously requested withdrawal once it is claimable. - * This request can be claimed once the withdrawal queue's `claimable` amount - * is greater than or equal this request's `queued` amount and 10 minutes has passed. - * If the requests is not claimable, the transaction will revert with `Queue pending liquidity`. - * If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`. - * OETH is converted to WETH at 1:1. - * @param _requestId Unique ID for the withdrawal request - * @return amount Amount of WETH transferred to the withdrawer - */ - function claimWithdrawal(uint256 _requestId) - external - virtual - whenNotCapitalPaused - nonReentrant - returns (uint256 amount) - { - // Try and get more liquidity if there is not enough available - if ( - withdrawalRequests[_requestId].queued > - withdrawalQueueMetadata.claimable - ) { - // Add any WETH to the withdrawal queue - // this needs to remain here as: - // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called - // - funds can be withdrawn from a strategy - // - // Those funds need to be added to withdrawal queue liquidity - _addWithdrawalQueueLiquidity(); - } - - amount = _claimWithdrawal(_requestId); - - // transfer WETH from the vault to the withdrawer - IERC20(weth).safeTransfer(msg.sender, amount); - - // Prevent insolvency - _postRedeem(amount); - } - - // slither-disable-end reentrancy-no-eth - - /** - * @notice Claim a previously requested withdrawals once they are claimable. - * This requests can be claimed once the withdrawal queue's `claimable` amount - * is greater than or equal each request's `queued` amount and 10 minutes has passed. - * If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`. - * If one of the requests is not older than 10 minutes, - * the whole transaction will revert with `Claim delay not met`. - * @param _requestIds Unique ID of each withdrawal request - * @return amounts Amount of WETH received for each request - * @return totalAmount Total amount of WETH transferred to the withdrawer - */ - function claimWithdrawals(uint256[] calldata _requestIds) - external - virtual - whenNotCapitalPaused - nonReentrant - returns (uint256[] memory amounts, uint256 totalAmount) - { - // Add any WETH to the withdrawal queue - // this needs to remain here as: - // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called - // - funds can be withdrawn from a strategy - // - // Those funds need to be added to withdrawal queue liquidity - _addWithdrawalQueueLiquidity(); - - amounts = new uint256[](_requestIds.length); - for (uint256 i; i < _requestIds.length; ++i) { - amounts[i] = _claimWithdrawal(_requestIds[i]); - totalAmount += amounts[i]; - } - - // transfer all the claimed WETH from the vault to the withdrawer - IERC20(weth).safeTransfer(msg.sender, totalAmount); - - // Prevent insolvency - _postRedeem(totalAmount); - } - - function _claimWithdrawal(uint256 requestId) - internal - returns (uint256 amount) - { - require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); - - // Load the structs from storage into memory - WithdrawalRequest memory request = withdrawalRequests[requestId]; - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - require( - request.timestamp + withdrawalClaimDelay <= block.timestamp, - "Claim delay not met" - ); - // If there isn't enough reserved liquidity in the queue to claim - require(request.queued <= queue.claimable, "Queue pending liquidity"); - require(request.withdrawer == msg.sender, "Not requester"); - require(request.claimed == false, "Already claimed"); - - // Store the request as claimed - withdrawalRequests[requestId].claimed = true; - // Store the updated claimed amount - withdrawalQueueMetadata.claimed = queue.claimed + request.amount; - - emit WithdrawalClaimed(msg.sender, requestId, request.amount); - - return request.amount; - } - - /// @notice Adds WETH to the withdrawal queue if there is a funding shortfall. - /// @dev is called from the Native Staking strategy when validator withdrawals are processed. - /// It also called before any WETH is allocated to a strategy. - function addWithdrawalQueueLiquidity() external { - _addWithdrawalQueueLiquidity(); - } - - /// @dev Adds WETH to the withdrawal queue if there is a funding shortfall. - /// This assumes 1 WETH equal 1 OETH. - function _addWithdrawalQueueLiquidity() - internal - returns (uint256 addedClaimable) - { - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // Check if the claimable WETH is less than the queued amount - uint256 queueShortfall = queue.queued - queue.claimable; - - // No need to do anything is the withdrawal queue is full funded - if (queueShortfall == 0) { - return 0; - } - - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - - // Of the claimable withdrawal requests, how much is unclaimed? - // That is, the amount of WETH that is currently allocated for the withdrawal queue - uint256 allocatedWeth = queue.claimable - queue.claimed; - - // If there is no unallocated WETH then there is nothing to add to the queue - if (wethBalance <= allocatedWeth) { - return 0; - } - - uint256 unallocatedWeth = wethBalance - allocatedWeth; - - // the new claimable amount is the smaller of the queue shortfall or unallocated weth - addedClaimable = queueShortfall < unallocatedWeth - ? queueShortfall - : unallocatedWeth; - uint256 newClaimable = queue.claimable + addedClaimable; - - // Store the new claimable amount back to storage - withdrawalQueueMetadata.claimable = SafeCast.toUint128(newClaimable); - - // emit a WithdrawalClaimable event - emit WithdrawalClaimable(newClaimable, addedClaimable); - } - - /*************************************** - View Functions - ****************************************/ - - /// @dev Calculate how much WETH in the vault is not reserved for the withdrawal queue. - // That is, it is available to be redeemed or deposited into a strategy. - function _wethAvailable() internal view returns (uint256 wethAvailable) { - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // The amount of WETH that is still to be claimed in the withdrawal queue - uint256 outstandingWithdrawals = queue.queued - queue.claimed; - - // The amount of sitting in WETH in the vault - uint256 wethBalance = IERC20(weth).balanceOf(address(this)); - - // If there is not enough WETH in the vault to cover the outstanding withdrawals - if (wethBalance <= outstandingWithdrawals) { - return 0; - } - - return wethBalance - outstandingWithdrawals; - } - - /// @dev Get the balance of an asset held in Vault and all strategies - /// less any WETH that is reserved for the withdrawal queue. - /// WETH is the only asset that can return a non-zero balance. - /// All other assets will return 0 even if there is some dust amounts left in the Vault. - /// For example, there is 1 wei left of stETH in the OETH Vault but will return 0 in this function. - /// - /// If there is not enough WETH in the vault and all strategies to cover all outstanding - /// withdrawal requests then return a WETH balance of 0 - function _checkBalance(address _asset) - internal - view - override - returns (uint256 balance) - { - if (_asset != weth) { - return 0; - } - - // Get the WETH in the vault and the strategies - balance = super._checkBalance(_asset); - - WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - - // If the vault becomes insolvent enough that the total value in the vault and all strategies - // is less than the outstanding withdrawals. - // For example, there was a mass slashing event and most users request a withdrawal. - if (balance + queue.claimed < queue.queued) { - return 0; - } - - // Need to remove WETH that is reserved for the withdrawal queue - return balance + queue.claimed - queue.queued; - } - - /** - * @notice Allocate unallocated funds on Vault to strategies. - **/ - function allocate() external override whenNotCapitalPaused nonReentrant { - // Add any unallocated WETH to the withdrawal queue first - _addWithdrawalQueueLiquidity(); - - _allocate(); - } - - /// @dev Allocate WETH to the default WETH strategy if there is excess to the Vault buffer. - /// This is called from either `mint` or `allocate` and assumes `_addWithdrawalQueueLiquidity` - /// has been called before this function. - function _allocate() internal override { - // No need to do anything if no default strategy for WETH - address depositStrategyAddr = assetDefaultStrategies[weth]; - if (depositStrategyAddr == address(0)) return; - - uint256 wethAvailableInVault = _wethAvailable(); - // No need to do anything if there isn't any WETH in the vault to allocate - if (wethAvailableInVault == 0) return; - - // Calculate the target buffer for the vault using the total supply - uint256 totalSupply = oUSD.totalSupply(); - uint256 targetBuffer = totalSupply.mulTruncate(vaultBuffer); - - // If available WETH in the Vault is below or equal the target buffer then there's nothing to allocate - if (wethAvailableInVault <= targetBuffer) return; - - // The amount of assets to allocate to the default strategy - uint256 allocateAmount = wethAvailableInVault - targetBuffer; - - IStrategy strategy = IStrategy(depositStrategyAddr); - // Transfer WETH to the strategy and call the strategy's deposit function - IERC20(weth).safeTransfer(address(strategy), allocateAmount); - strategy.deposit(weth, allocateAmount); - - emit AssetAllocated(weth, depositStrategyAddr, allocateAmount); - } - - /// @dev The total value of all WETH held by the vault and all its strategies - /// less any WETH that is reserved for the withdrawal queue. - /// - // If there is not enough WETH in the vault and all strategies to cover all outstanding - // withdrawal requests then return a total value of 0. - function _totalValue() internal view override returns (uint256 value) { - // As WETH is the only asset, just return the WETH balance - return _checkBalance(weth); - } - - /// @dev Only WETH is supported in the OETH Vault so return the WETH balance only - /// Any ETH balances in the Vault will be ignored. - /// Amounts from previously supported vault assets will also be ignored. - /// For example, there is 1 wei left of stETH in the OETH Vault but is will be ignored. - function _totalValueInVault() - internal - view - override - returns (uint256 value) - { - value = IERC20(weth).balanceOf(address(this)); - } -} diff --git a/contracts/contracts/vault/OSVault.sol b/contracts/contracts/vault/OSVault.sol new file mode 100644 index 0000000000..c965871034 --- /dev/null +++ b/contracts/contracts/vault/OSVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title Origin Sonic VaultAdmin contract on Sonic + * @author Origin Protocol Inc + */ +contract OSVault is VaultAdmin { + constructor(address _wS) VaultAdmin(_wS) {} +} diff --git a/contracts/contracts/vault/OSonicVaultAdmin.sol b/contracts/contracts/vault/OSonicVaultAdmin.sol deleted file mode 100644 index 445ba03550..0000000000 --- a/contracts/contracts/vault/OSonicVaultAdmin.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultAdmin } from "./OETHVaultAdmin.sol"; - -/** - * @title Origin Sonic VaultAdmin contract on Sonic - * @author Origin Protocol Inc - */ -contract OSonicVaultAdmin is OETHVaultAdmin { - /// @param _wS Sonic's Wrapped S token - constructor(address _wS) OETHVaultAdmin(_wS) {} - - /*************************************** - Asset Config - ****************************************/ - - /** - * @notice Add a supported asset to the contract, i.e. one that can be to mint OTokens. - * @dev Overridden to remove price provider integration - * @param _asset Address of asset - * @param _unitConversion 0 decimals, 1 exchange rate - */ - function supportAsset(address _asset, uint8 _unitConversion) - external - override - onlyGovernor - { - require(!assets[_asset].isSupported, "Asset already supported"); - - assets[_asset] = Asset({ - isSupported: true, - unitConversion: UnitConversion(_unitConversion), - decimals: 0, // will be overridden in _cacheDecimals - allowedOracleSlippageBps: 0 // 0% by default - }); - - _cacheDecimals(_asset); - allAssets.push(_asset); - - emit AssetSupported(_asset); - } -} diff --git a/contracts/contracts/vault/OSonicVaultCore.sol b/contracts/contracts/vault/OSonicVaultCore.sol deleted file mode 100644 index f35b7b5cd5..0000000000 --- a/contracts/contracts/vault/OSonicVaultCore.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import { OETHVaultCore } from "./OETHVaultCore.sol"; - -/** - * @title Origin Sonic VaultCore contract on Sonic - * @author Origin Protocol Inc - */ -contract OSonicVaultCore is OETHVaultCore { - /// @param _wS Sonic's Wrapped S token - constructor(address _wS) OETHVaultCore(_wS) {} - - /** - * @notice Instant redeem is not supported on Sonic. - * Use the asynchronous `requestWithdrawal` a `claimWithdrawal` instead. - */ - function _redeem(uint256, uint256) internal override { - revert("unsupported function"); - } -} diff --git a/contracts/contracts/vault/OUSDVault.sol b/contracts/contracts/vault/OUSDVault.sol new file mode 100644 index 0000000000..a800636b91 --- /dev/null +++ b/contracts/contracts/vault/OUSDVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { VaultAdmin } from "./VaultAdmin.sol"; + +/** + * @title OUSD VaultAdmin Contract + * @author Origin Protocol Inc + */ +contract OUSDVault is VaultAdmin { + constructor(address _usdc) VaultAdmin(_usdc) {} +} diff --git a/contracts/contracts/vault/README.md b/contracts/contracts/vault/README.md index 651a49f2d6..868f4d7b2a 100644 --- a/contracts/contracts/vault/README.md +++ b/contracts/contracts/vault/README.md @@ -2,60 +2,14 @@ ## Hierarchy -![Vault Core Hierarchy](../../docs/VaultHierarchy.svg) +![Vault Hierarchy](../../docs/VaultHierarchy.svg) -## OUSD Vault +## Vault -## Vault Core Squashed +## Vault Squashed -![Vault Core Squashed](../../docs/VaultCoreSquashed.svg) +![Vault Squashed](../../docs/VaultSquashed.svg) ### Storage ![Vault Storage](../../docs/VaultStorage.svg) - -## Vault Admin Squashed - -![Vault Admin Squashed](../../docs/VaultAdminSquashed.svg) - -## OETH Vault - -## Vault Core Squashed - -![OETH Vault Core Squashed](../../docs/OETHVaultCoreSquashed.svg) - -## Vault Admin Squashed - -![OETH Vault Admin Squashed](../../docs/OETHVaultAdminSquashed.svg) - -### Storage - -![OETH Vault Storage](../../docs/OETHVaultStorage.svg) - -## Base OETH Vault - -## Vault Core Squashed - -![OETH Vault Core Squashed](../../docs/OETHBaseVaultCoreSquashed.svg) - -## Vault Admin Squashed - -![OETH Vault Admin Squashed](../../docs/OETHBaseVaultAdminSquashed.svg) - -### Storage - -![OETH Vault Storage](../../docs/OETHBaseVaultStorage.svg) - -## Origin Sonic (OS) Vault - -## Vault Core Squashed - -![Sonic Vault Core Squashed](../../docs/OSonicVaultCoreSquashed.svg) - -## Vault Admin Squashed - -![Sonic Vault Admin Squashed](../../docs/OSonicVaultAdminSquashed.svg) - -### Storage - -![Sonic Vault Storage](../../docs/OSonicVaultStorage.svg) \ No newline at end of file diff --git a/contracts/contracts/vault/Vault.sol b/contracts/contracts/vault/Vault.sol deleted file mode 100644 index 7753403040..0000000000 --- a/contracts/contracts/vault/Vault.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -/** - * @title OUSD VaultInitializer Contract - * @notice The VaultInitializer sets up the initial contract. - * @author Origin Protocol Inc - */ -import { VaultInitializer } from "./VaultInitializer.sol"; -import { VaultAdmin } from "./VaultAdmin.sol"; - -contract Vault is VaultInitializer, VaultAdmin {} diff --git a/contracts/contracts/vault/VaultAdmin.sol b/contracts/contracts/vault/VaultAdmin.sol index fc402d97e4..1c42f74613 100644 --- a/contracts/contracts/vault/VaultAdmin.sol +++ b/contracts/contracts/vault/VaultAdmin.sol @@ -9,15 +9,13 @@ pragma solidity ^0.8.0; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; -import { ISwapper } from "../interfaces/ISwapper.sol"; import { IVault } from "../interfaces/IVault.sol"; import { StableMath } from "../utils/StableMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import "./VaultStorage.sol"; +import "./VaultCore.sol"; -contract VaultAdmin is VaultStorage { +abstract contract VaultAdmin is VaultCore { using SafeERC20 for IERC20; using StableMath for uint256; using SafeCast for uint256; @@ -33,32 +31,14 @@ contract VaultAdmin is VaultStorage { _; } + constructor(address _asset) VaultCore(_asset) {} + /*************************************** Configuration ****************************************/ - - /** - * @notice Set address of price provider. - * @param _priceProvider Address of price provider - */ - function setPriceProvider(address _priceProvider) external onlyGovernor { - priceProvider = _priceProvider; - emit PriceProviderUpdated(_priceProvider); - } - /** - * @notice Set a fee in basis points to be charged for a redeem. - * @param _redeemFeeBps Basis point fee to be charged - */ - function setRedeemFeeBps(uint256 _redeemFeeBps) external onlyGovernor { - require(_redeemFeeBps <= 1000, "Redeem fee should not be over 10%"); - redeemFeeBps = _redeemFeeBps; - emit RedeemFeeUpdated(_redeemFeeBps); - } - - /** - * @notice Set a buffer of assets to keep in the Vault to handle most - * redemptions without needing to spend gas unwinding assets from a Strategy. + * @notice Set a buffer of asset to keep in the Vault to handle most + * redemptions without needing to spend gas unwinding asset from a Strategy. * @param _vaultBuffer Percentage using 18 decimals. 100% = 1e18. */ function setVaultBuffer(uint256 _vaultBuffer) @@ -103,57 +83,26 @@ contract VaultAdmin is VaultStorage { } /** - * @notice Set the default Strategy for an asset, i.e. the one which the asset - will be automatically allocated to and withdrawn from - * @param _asset Address of the asset + * @notice Set the default Strategy for asset, i.e. the one which + * the asset will be automatically allocated to and withdrawn from * @param _strategy Address of the Strategy */ - function setAssetDefaultStrategy(address _asset, address _strategy) + function setDefaultStrategy(address _strategy) external onlyGovernorOrStrategist { - emit AssetDefaultStrategyUpdated(_asset, _strategy); + emit DefaultStrategyUpdated(_strategy); // If its a zero address being passed for the strategy we are removing // the default strategy if (_strategy != address(0)) { // Make sure the strategy meets some criteria require(strategies[_strategy].isSupported, "Strategy not approved"); - IStrategy strategy = IStrategy(_strategy); - require(assets[_asset].isSupported, "Asset is not supported"); require( - strategy.supportsAsset(_asset), + IStrategy(_strategy).supportsAsset(asset), "Asset not supported by Strategy" ); } - assetDefaultStrategies[_asset] = _strategy; - } - - /** - * @notice Set maximum amount of OTokens that can at any point be minted and deployed - * to strategy (used only by ConvexOUSDMetaStrategy for now). - * @param _threshold OToken amount with 18 fixed decimals. - */ - function setNetOusdMintForStrategyThreshold(uint256 _threshold) - external - onlyGovernor - { - /** - * Because `netOusdMintedForStrategy` check in vault core works both ways - * (positive and negative) the actual impact of the amount of OToken minted - * could be double the threshold. E.g.: - * - contract has threshold set to 100 - * - state of netOusdMinted is -90 - * - in effect it can mint 190 OToken and still be within limits - * - * We are somewhat mitigating this behaviour by resetting the netOusdMinted - * counter whenever new threshold is set. So it can only move one threshold - * amount in each direction. This also enables us to reduce the threshold - * amount and not have problems with current netOusdMinted being near - * limits on either side. - */ - netOusdMintedForStrategy = 0; - netOusdMintForStrategyThreshold = _threshold; - emit NetOusdMintForStrategyThresholdChanged(_threshold); + defaultStrategy = _strategy; } /** @@ -170,6 +119,7 @@ contract VaultAdmin is VaultStorage { emit WithdrawalClaimDelayUpdated(_delay); } + // slither-disable-start reentrancy-no-eth /** * @notice Set a yield streaming max rate. This spreads yield over * time if it is above the max rate. @@ -180,7 +130,7 @@ contract VaultAdmin is VaultStorage { onlyGovernorOrStrategist { // The old yield will be at the old rate - IVault(address(this)).rebase(); + _rebase(); // Change the rate uint256 newPerSecond = yearlyApr / 100 / 365 days; require(newPerSecond <= MAX_REBASE_PER_SECOND, "Rate too high"); @@ -188,6 +138,9 @@ contract VaultAdmin is VaultStorage { emit RebasePerSecondMaxChanged(newPerSecond); } + // slither-disable-end reentrancy-no-eth + + // slither-disable-start reentrancy-no-eth /** * @notice Set the drip duration period * @param _dripDuration Time in seconds to target a constant yield rate @@ -197,271 +150,12 @@ contract VaultAdmin is VaultStorage { onlyGovernorOrStrategist { // The old yield will be at the old rate - IVault(address(this)).rebase(); + _rebase(); dripDuration = _dripDuration.toUint64(); emit DripDurationChanged(_dripDuration); } - /*************************************** - Swaps - ****************************************/ - - /** - * @notice Strategist swaps collateral assets sitting in the vault. - * @param _fromAsset The token address of the asset being sold by the vault. - * @param _toAsset The token address of the asset being purchased by the vault. - * @param _fromAssetAmount The amount of assets being sold by the vault. - * @param _minToAssetAmount The minimum amount of assets to be purchased. - * @param _data implementation specific data. eg 1Inch swap data - * @return toAssetAmount The amount of toAssets that was received from the swap - */ - function swapCollateral( - address _fromAsset, - address _toAsset, - uint256 _fromAssetAmount, - uint256 _minToAssetAmount, - bytes calldata _data - ) - external - nonReentrant - onlyGovernorOrStrategist - returns (uint256 toAssetAmount) - { - toAssetAmount = _swapCollateral( - _fromAsset, - _toAsset, - _fromAssetAmount, - _minToAssetAmount, - _data - ); - } - - function _swapCollateral( - address _fromAsset, - address _toAsset, - uint256 _fromAssetAmount, - uint256 _minToAssetAmount, - bytes calldata _data - ) internal virtual returns (uint256 toAssetAmount) { - // Check fromAsset and toAsset are valid - Asset memory fromAssetConfig = assets[_fromAsset]; - Asset memory toAssetConfig = assets[_toAsset]; - require(fromAssetConfig.isSupported, "From asset is not supported"); - require(toAssetConfig.isSupported, "To asset is not supported"); - - // Load swap config into memory to avoid separate SLOADs - SwapConfig memory config = swapConfig; - - // Scope a new block to remove toAssetBalBefore from the scope of swapCollateral. - // This avoids a stack too deep error. - { - uint256 toAssetBalBefore = IERC20(_toAsset).balanceOf( - address(this) - ); - - // Transfer from assets to the swapper contract - IERC20(_fromAsset).safeTransfer(config.swapper, _fromAssetAmount); - - // Call to the Swapper contract to do the actual swap - // The -1 is required for stETH which sometimes transfers 1 wei less than what was specified. - // slither-disable-next-line unused-return - ISwapper(config.swapper).swap( - _fromAsset, - _toAsset, - _fromAssetAmount - 1, - _minToAssetAmount, - _data - ); - - // Compute the change in asset balance held by the Vault - toAssetAmount = - IERC20(_toAsset).balanceOf(address(this)) - - toAssetBalBefore; - } - - // Check the to assets returned is above slippage amount specified by the strategist - require( - toAssetAmount >= _minToAssetAmount, - "Strategist slippage limit" - ); - - // Scope a new block to remove minOracleToAssetAmount from the scope of swapCollateral. - // This avoids a stack too deep error. - { - // Check the slippage against the Oracle in case the strategist made a mistake or has become malicious. - // to asset amount = from asset amount * from asset price / to asset price - uint256 minOracleToAssetAmount = (_fromAssetAmount * - (1e4 - fromAssetConfig.allowedOracleSlippageBps) * - IOracle(priceProvider).price(_fromAsset)) / - (IOracle(priceProvider).price(_toAsset) * - (1e4 + toAssetConfig.allowedOracleSlippageBps)); - - // Scale both sides up to 18 decimals to compare - require( - toAssetAmount.scaleBy(18, toAssetConfig.decimals) >= - minOracleToAssetAmount.scaleBy( - 18, - fromAssetConfig.decimals - ), - "Oracle slippage limit exceeded" - ); - } - - // Check the vault's total value hasn't gone below the OToken total supply - // by more than the allowed percentage. - require( - IVault(address(this)).totalValue() >= - (oUSD.totalSupply() * ((1e4 - config.allowedUndervalueBps))) / - 1e4, - "Allowed value < supply" - ); - - emit Swapped(_fromAsset, _toAsset, _fromAssetAmount, toAssetAmount); - } - - /*************************************** - Swap Config - ****************************************/ - - /** - * @notice Set the contract the performs swaps of collateral assets. - * @param _swapperAddr Address of the Swapper contract that implements the ISwapper interface. - */ - function setSwapper(address _swapperAddr) external onlyGovernor { - swapConfig.swapper = _swapperAddr; - emit SwapperChanged(_swapperAddr); - } - - /// @notice Contract that swaps the vault's collateral assets - function swapper() external view returns (address swapper_) { - swapper_ = swapConfig.swapper; - } - - /** - * @notice Set max allowed percentage the vault total value can drop below the OToken total supply in basis points - * when executing collateral swaps. - * @param _basis Percentage in basis points. eg 100 == 1% - */ - function setSwapAllowedUndervalue(uint16 _basis) external onlyGovernor { - require(_basis < 10001, "Invalid basis points"); - swapConfig.allowedUndervalueBps = _basis; - emit SwapAllowedUndervalueChanged(_basis); - } - - /** - * @notice Max allowed percentage the vault total value can drop below the OToken total supply in basis points - * when executing a collateral swap. - * For example 100 == 1% - * @return value Percentage in basis points. - */ - function allowedSwapUndervalue() external view returns (uint256 value) { - value = swapConfig.allowedUndervalueBps; - } - - /** - * @notice Set the allowed slippage from the Oracle price for collateral asset swaps. - * @param _asset Address of the asset token. - * @param _allowedOracleSlippageBps allowed slippage from Oracle in basis points. eg 20 = 0.2%. Max 10%. - */ - function setOracleSlippage(address _asset, uint16 _allowedOracleSlippageBps) - external - onlyGovernor - { - require(assets[_asset].isSupported, "Asset not supported"); - require(_allowedOracleSlippageBps < 1000, "Slippage too high"); - - assets[_asset].allowedOracleSlippageBps = _allowedOracleSlippageBps; - - emit SwapSlippageChanged(_asset, _allowedOracleSlippageBps); - } - - /*************************************** - Asset Config - ****************************************/ - - /** - * @notice Add a supported asset to the contract, i.e. one that can be - * to mint OTokens. - * @param _asset Address of asset - */ - function supportAsset(address _asset, uint8 _unitConversion) - external - virtual - onlyGovernor - { - require(!assets[_asset].isSupported, "Asset already supported"); - - assets[_asset] = Asset({ - isSupported: true, - unitConversion: UnitConversion(_unitConversion), - decimals: 0, // will be overridden in _cacheDecimals - allowedOracleSlippageBps: 0 // 0% by default - }); - - _cacheDecimals(_asset); - allAssets.push(_asset); - - // Verify that our oracle supports the asset - // slither-disable-next-line unused-return - IOracle(priceProvider).price(_asset); - - emit AssetSupported(_asset); - } - - /** - * @notice Remove a supported asset from the Vault - * @param _asset Address of asset - */ - function removeAsset(address _asset) external onlyGovernor { - require(assets[_asset].isSupported, "Asset not supported"); - - // 1e13 for 18 decimals. And 10 for 6 decimals - uint256 maxDustBalance = uint256(1e13).scaleBy( - assets[_asset].decimals, - 18 - ); - - require( - IVault(address(this)).checkBalance(_asset) <= maxDustBalance, - "Vault still holds asset" - ); - - uint256 assetsCount = allAssets.length; - uint256 assetIndex = assetsCount; // initialize at invalid index - for (uint256 i = 0; i < assetsCount; ++i) { - if (allAssets[i] == _asset) { - assetIndex = i; - break; - } - } - - // Note: If asset is not found in `allAssets`, the following line - // will revert with an out-of-bound error. However, there's no - // reason why an asset would have `Asset.isSupported = true` but - // not exist in `allAssets`. - - // Update allAssets array - allAssets[assetIndex] = allAssets[assetsCount - 1]; - allAssets.pop(); - - // Reset default strategy - assetDefaultStrategies[_asset] = address(0); - emit AssetDefaultStrategyUpdated(_asset, address(0)); - - // Remove asset from storage - delete assets[_asset]; - - emit AssetRemoved(_asset); - } - - /** - * @notice Cache decimals on OracleRouter for a particular asset. This action - * is required before that asset's price can be accessed. - * @param _asset Address of asset token - */ - function cacheDecimals(address _asset) external onlyGovernor { - _cacheDecimals(_asset); - } + // slither-disable-end reentrancy-no-eth /*************************************** Strategy Config @@ -485,14 +179,7 @@ contract VaultAdmin is VaultStorage { function removeStrategy(address _addr) external onlyGovernor { require(strategies[_addr].isSupported, "Strategy not approved"); - - uint256 assetCount = allAssets.length; - for (uint256 i = 0; i < assetCount; ++i) { - require( - assetDefaultStrategies[allAssets[i]] != _addr, - "Strategy is default for an asset" - ); - } + require(defaultStrategy != _addr, "Strategy is default for asset"); // Initialize strategyIndex with out of bounds result so function will // revert if no valid index found @@ -512,7 +199,7 @@ contract VaultAdmin is VaultStorage { // Mark the strategy as not supported strategies[_addr].isSupported = false; - // Withdraw all assets + // Withdraw all asset IStrategy strategy = IStrategy(_addr); strategy.withdrawAll(); @@ -520,13 +207,52 @@ contract VaultAdmin is VaultStorage { } } + /** + * @notice Adds a strategy to the mint whitelist. + * Reverts if strategy isn't approved on Vault. + * @param strategyAddr Strategy address + */ + function addStrategyToMintWhitelist(address strategyAddr) + external + onlyGovernor + { + require(strategies[strategyAddr].isSupported, "Strategy not approved"); + + require( + !isMintWhitelistedStrategy[strategyAddr], + "Already whitelisted" + ); + + isMintWhitelistedStrategy[strategyAddr] = true; + + emit StrategyAddedToMintWhitelist(strategyAddr); + } + + /** + * @notice Removes a strategy from the mint whitelist. + * @param strategyAddr Strategy address + */ + function removeStrategyFromMintWhitelist(address strategyAddr) + external + onlyGovernor + { + // Intentionally skipping `strategies.isSupported` check since + // we may wanna remove an address even after removing the strategy + + require(isMintWhitelistedStrategy[strategyAddr], "Not whitelisted"); + + isMintWhitelistedStrategy[strategyAddr] = false; + + emit StrategyRemovedFromMintWhitelist(strategyAddr); + } + /*************************************** Strategies ****************************************/ /** - * @notice Deposit multiple assets from the vault into the strategy. - * @param _strategyToAddress Address of the Strategy to deposit assets into. + * @notice Deposit multiple asset from the vault into the strategy. + * @param _strategyToAddress Address of the Strategy to deposit asset into. * @param _assets Array of asset address that will be deposited into the strategy. * @param _amounts Array of amounts of each corresponding asset to deposit. */ @@ -547,26 +273,28 @@ contract VaultAdmin is VaultStorage { strategies[_strategyToAddress].isSupported, "Invalid to Strategy" ); - require(_assets.length == _amounts.length, "Parameter length mismatch"); + require( + _assets.length == 1 && _amounts.length == 1 && _assets[0] == asset, + "Only asset is supported" + ); - uint256 assetCount = _assets.length; - for (uint256 i = 0; i < assetCount; ++i) { - address assetAddr = _assets[i]; - require( - IStrategy(_strategyToAddress).supportsAsset(assetAddr), - "Asset unsupported" - ); - // Send required amount of funds to the strategy - IERC20(assetAddr).safeTransfer(_strategyToAddress, _amounts[i]); - } + // Check the there is enough asset to transfer once the backing + // asset reserved for the withdrawal queue is accounted for + require( + _amounts[0] <= _assetAvailable(), + "Not enough assets available" + ); + + // Send required amount of funds to the strategy + IERC20(asset).safeTransfer(_strategyToAddress, _amounts[0]); // Deposit all the funds that have been sent to the strategy IStrategy(_strategyToAddress).depositAll(); } /** - * @notice Withdraw multiple assets from the strategy to the vault. - * @param _strategyFromAddress Address of the Strategy to withdraw assets from. + * @notice Withdraw multiple asset from the strategy to the vault. + * @param _strategyFromAddress Address of the Strategy to withdraw asset from. * @param _assets Array of asset address that will be withdrawn from the strategy. * @param _amounts Array of amounts of each corresponding asset to withdraw. */ @@ -607,11 +335,13 @@ contract VaultAdmin is VaultStorage { _amounts[i] ); } + + _addWithdrawalQueueLiquidity(); } /** * @notice Sets the maximum allowable difference between - * total supply and backing assets' value. + * total supply and asset' value. */ function setMaxSupplyDiff(uint256 _maxSupplyDiff) external onlyGovernor { maxSupplyDiff = _maxSupplyDiff; @@ -637,18 +367,6 @@ contract VaultAdmin is VaultStorage { emit TrusteeFeeBpsChanged(_basis); } - /** - * @notice Set OToken Metapool strategy - * @param _ousdMetaStrategy Address of OToken metapool strategy - */ - function setOusdMetaStrategy(address _ousdMetaStrategy) - external - onlyGovernor - { - ousdMetaStrategy = _ousdMetaStrategy; - emit OusdMetaStrategyUpdated(_ousdMetaStrategy); - } - /*************************************** Pause ****************************************/ @@ -699,7 +417,7 @@ contract VaultAdmin is VaultStorage { external onlyGovernor { - require(!assets[_asset].isSupported, "Only unsupported assets"); + require(asset != _asset, "Only unsupported asset"); IERC20(_asset).safeTransfer(governor(), _amount); } @@ -708,7 +426,7 @@ contract VaultAdmin is VaultStorage { ****************************************/ /** - * @notice Withdraws all assets from the strategy and sends assets to the Vault. + * @notice Withdraws all asset from the strategy and sends asset to the Vault. * @param _strategyAddr Strategy address. */ function withdrawAllFromStrategy(address _strategyAddr) @@ -725,10 +443,11 @@ contract VaultAdmin is VaultStorage { ); IStrategy strategy = IStrategy(_strategyAddr); strategy.withdrawAll(); + _addWithdrawalQueueLiquidity(); } /** - * @notice Withdraws all assets from all the strategies and sends assets to the Vault. + * @notice Withdraws all asset from all the strategies and sends asset to the Vault. */ function withdrawAllFromStrategies() external onlyGovernorOrStrategist { _withdrawAllFromStrategies(); @@ -739,19 +458,6 @@ contract VaultAdmin is VaultStorage { for (uint256 i = 0; i < stratCount; ++i) { IStrategy(allStrategies[i]).withdrawAll(); } - } - - /*************************************** - Utils - ****************************************/ - - function _cacheDecimals(address token) internal { - Asset storage tokenAsset = assets[token]; - if (tokenAsset.decimals != 0) { - return; - } - uint8 decimals = IBasicToken(token).decimals(); - require(decimals >= 6 && decimals <= 18, "Unexpected precision"); - tokenAsset.decimals = decimals; + _addWithdrawalQueueLiquidity(); } } diff --git a/contracts/contracts/vault/VaultCore.sol b/contracts/contracts/vault/VaultCore.sol index ef1abed76a..0be201183d 100644 --- a/contracts/contracts/vault/VaultCore.sol +++ b/contracts/contracts/vault/VaultCore.sol @@ -3,27 +3,24 @@ pragma solidity ^0.8.0; /** * @title OToken VaultCore contract - * @notice The Vault contract stores assets. On a deposit, OTokens will be minted + * @notice The Vault contract stores asset. On a deposit, OTokens will be minted and sent to the depositor. On a withdrawal, OTokens will be burned and - assets will be sent to the withdrawer. The Vault accepts deposits of + asset will be sent to the withdrawer. The Vault accepts deposits of interest from yield bearing strategies which will modify the supply of OTokens. * @author Origin Protocol Inc */ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { StableMath } from "../utils/StableMath.sol"; -import { IOracle } from "../interfaces/IOracle.sol"; -import { IGetExchangeRateToken } from "../interfaces/IGetExchangeRateToken.sol"; import "./VaultInitializer.sol"; -contract VaultCore is VaultInitializer { +abstract contract VaultCore is VaultInitializer { using SafeERC20 for IERC20; using StableMath for uint256; - /// @dev max signed int - uint256 internal constant MAX_INT = uint256(type(int256).max); /** * @dev Verifies that the rebasing is not paused. @@ -41,81 +38,74 @@ contract VaultCore is VaultInitializer { _; } - /** - * @dev Verifies that the caller is the AMO strategy. - */ - modifier onlyOusdMetaStrategy() { - require( - msg.sender == ousdMetaStrategy, - "Caller is not the OUSD meta strategy" - ); - _; - } + constructor(address _asset) VaultInitializer(_asset) {} + //////////////////////////////////////////////////// + /// MINT / BURN /// + //////////////////////////////////////////////////// /** * @notice Deposit a supported asset and mint OTokens. - * @param _asset Address of the asset being deposited + * @dev Deprecated: use `mint(uint256 _amount)` instead. + * @dev Deprecated: param _asset Address of the asset being deposited * @param _amount Amount of the asset being deposited - * @param _minimumOusdAmount Minimum OTokens to mint + * @dev Deprecated: param _minimumOusdAmount Minimum OTokens to mint */ function mint( - address _asset, + address, uint256 _amount, - uint256 _minimumOusdAmount + uint256 ) external whenNotCapitalPaused nonReentrant { - _mint(_asset, _amount, _minimumOusdAmount); + _mint(_amount); + } + + /** + * @notice Deposit a supported asset and mint OTokens. + * @param _amount Amount of the asset being deposited + */ + function mint(uint256 _amount) external whenNotCapitalPaused nonReentrant { + _mint(_amount); } + // slither-disable-start reentrancy-no-eth /** * @dev Deposit a supported asset and mint OTokens. - * @param _asset Address of the asset being deposited * @param _amount Amount of the asset being deposited - * @param _minimumOusdAmount Minimum OTokens to mint */ - function _mint( - address _asset, - uint256 _amount, - uint256 _minimumOusdAmount - ) internal virtual { - require(assets[_asset].isSupported, "Asset is not supported"); + function _mint(uint256 _amount) internal virtual { require(_amount > 0, "Amount must be greater than 0"); - uint256 units = _toUnits(_amount, _asset); - uint256 unitPrice = _toUnitPrice(_asset, true); - uint256 priceAdjustedDeposit = (units * unitPrice) / 1e18; - - if (_minimumOusdAmount > 0) { - require( - priceAdjustedDeposit >= _minimumOusdAmount, - "Mint amount lower than minimum" - ); - } + // Scale amount to 18 decimals + uint256 scaledAmount = _amount.scaleBy(18, assetDecimals); - emit Mint(msg.sender, priceAdjustedDeposit); + emit Mint(msg.sender, scaledAmount); // Rebase must happen before any transfers occur. - if (priceAdjustedDeposit >= rebaseThreshold && !rebasePaused) { + if (!rebasePaused && scaledAmount >= rebaseThreshold) { _rebase(); } - // Mint matching amount of OTokens - oUSD.mint(msg.sender, priceAdjustedDeposit); + // Mint oTokens + oUSD.mint(msg.sender, scaledAmount); - // Transfer the deposited coins to the vault - IERC20 asset = IERC20(_asset); - asset.safeTransferFrom(msg.sender, address(this), _amount); + IERC20(asset).safeTransferFrom(msg.sender, address(this), _amount); + + // Give priority to the withdrawal queue for the new asset liquidity + _addWithdrawalQueueLiquidity(); - if (priceAdjustedDeposit >= autoAllocateThreshold) { + // Auto-allocate if necessary + if (scaledAmount >= autoAllocateThreshold) { _allocate(); } } + // slither-disable-end reentrancy-no-eth + /** - * @notice Mint OTokens for a Metapool Strategy - * @param _amount Amount of the asset being deposited + * @notice Mint OTokens for an allowed Strategy + * @param _amount Amount of OToken to mint * * Notice: can't use `nonReentrant` modifier since the `mint` function can - * call `allocate`, and that can trigger `ConvexOUSDMetaStrategy` to call this function + * call `allocate`, and that can trigger an AMO strategy to call this function * while the execution of the `mint` has not yet completed -> causing a `nonReentrant` collision. * * Also important to understand is that this is a limitation imposed by the test suite. @@ -127,94 +117,232 @@ contract VaultCore is VaultInitializer { external virtual whenNotCapitalPaused - onlyOusdMetaStrategy { - require(_amount < MAX_INT, "Amount too high"); - - emit Mint(msg.sender, _amount); - - // safe to cast because of the require check at the beginning of the function - netOusdMintedForStrategy += int256(_amount); - require( - abs(netOusdMintedForStrategy) < netOusdMintForStrategyThreshold, - "Minted ousd surpassed netOusdMintForStrategyThreshold." + strategies[msg.sender].isSupported == true, + "Unsupported strategy" + ); + require( + isMintWhitelistedStrategy[msg.sender] == true, + "Not whitelisted strategy" ); + emit Mint(msg.sender, _amount); // Mint matching amount of OTokens oUSD.mint(msg.sender, _amount); } - // In memoriam + /** + * @notice Burn OTokens for an allowed Strategy + * @param _amount Amount of OToken to burn + * + * Todo: Maybe this is a comment that we can remove now? + * @dev Notice: can't use `nonReentrant` modifier since the `redeem` function could + * require withdrawal on an AMO strategy and that one can call `burnForStrategy` + * while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision. + * + * Also important to understand is that this is a limitation imposed by the test suite. + * Production / mainnet contracts should never be configured in a way where mint/redeem functions + * that are moving funds between the Vault and end user wallets can influence strategies + * utilizing this function. + */ + function burnForStrategy(uint256 _amount) + external + virtual + whenNotCapitalPaused + { + require( + strategies[msg.sender].isSupported == true, + "Unsupported strategy" + ); + require( + isMintWhitelistedStrategy[msg.sender] == true, + "Not whitelisted strategy" + ); + emit Redeem(msg.sender, _amount); + + // Burn OTokens + oUSD.burn(msg.sender, _amount); + } + + //////////////////////////////////////////////////// + /// ASYNC WITHDRAWALS /// + //////////////////////////////////////////////////// /** - * @notice Withdraw a supported asset and burn OTokens. - * @param _amount Amount of OTokens to burn - * @param _minimumUnitAmount Minimum stablecoin units to receive in return + * @notice Request an asynchronous withdrawal of asset in exchange for OToken. + * The OToken is burned on request and the asset is transferred to the withdrawer on claim. + * This request can be claimed once the withdrawal queue's `claimable` amount + * is greater than or equal this request's `queued` amount. + * There is a minimum of 10 minutes before a request can be claimed. After that, the request just needs + * enough asset liquidity in the Vault to satisfy all the outstanding requests to that point in the queue. + * OToken is converted to asset at 1:1. + * @param _amount Amount of OToken to burn. + * @return requestId Unique ID for the withdrawal request + * @return queued Cumulative total of all asset queued including already claimed requests. */ - function redeem(uint256 _amount, uint256 _minimumUnitAmount) + function requestWithdrawal(uint256 _amount) external + virtual whenNotCapitalPaused nonReentrant + returns (uint256 requestId, uint256 queued) { - _redeem(_amount, _minimumUnitAmount); + require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); + + // The check that the requester has enough OToken is done in to later burn call + + requestId = withdrawalQueueMetadata.nextWithdrawalIndex; + queued = + withdrawalQueueMetadata.queued + + _amount.scaleBy(assetDecimals, 18); + + // Store the next withdrawal request + withdrawalQueueMetadata.nextWithdrawalIndex = SafeCast.toUint128( + requestId + 1 + ); + // Store the updated queued amount which reserves asset in the withdrawal queue + // and reduces the vault's total asset + withdrawalQueueMetadata.queued = SafeCast.toUint128(queued); + // Store the user's withdrawal request + // `queued` is in asset decimals, while `amount` is in OToken decimals (18) + withdrawalRequests[requestId] = WithdrawalRequest({ + withdrawer: msg.sender, + claimed: false, + timestamp: uint40(block.timestamp), + amount: SafeCast.toUint128(_amount), + queued: SafeCast.toUint128(queued) + }); + + // Burn the user's OToken + oUSD.burn(msg.sender, _amount); + + // Prevent withdrawal if the vault is solvent by more than the allowed percentage + _postRedeem(_amount); + + emit WithdrawalRequested(msg.sender, requestId, _amount, queued); } + // slither-disable-start reentrancy-no-eth /** - * @notice Withdraw a supported asset and burn OTokens. - * @param _amount Amount of OTokens to burn - * @param _minimumUnitAmount Minimum stablecoin units to receive in return + * @notice Claim a previously requested withdrawal once it is claimable. + * This request can be claimed once the withdrawal queue's `claimable` amount + * is greater than or equal this request's `queued` amount and 10 minutes has passed. + * If the requests is not claimable, the transaction will revert with `Queue pending liquidity`. + * If the request is not older than 10 minutes, the transaction will revert with `Claim delay not met`. + * OToken is converted to asset at 1:1. + * @param _requestId Unique ID for the withdrawal request + * @return amount Amount of asset transferred to the withdrawer */ - function _redeem(uint256 _amount, uint256 _minimumUnitAmount) - internal + function claimWithdrawal(uint256 _requestId) + external virtual + whenNotCapitalPaused + nonReentrant + returns (uint256 amount) { - // Calculate redemption outputs - uint256[] memory outputs = _calculateRedeemOutputs(_amount); + // Try and get more liquidity if there is not enough available + if ( + withdrawalRequests[_requestId].queued > + withdrawalQueueMetadata.claimable + ) { + // Add any asset to the withdrawal queue + // this needs to remain here as: + // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called + // - funds can be withdrawn from a strategy + // + // Those funds need to be added to withdrawal queue liquidity + _addWithdrawalQueueLiquidity(); + } - emit Redeem(msg.sender, _amount); + // Scale amount to asset decimals + amount = _claimWithdrawal(_requestId); - // Send outputs - uint256 assetCount = allAssets.length; - for (uint256 i = 0; i < assetCount; ++i) { - if (outputs[i] == 0) continue; - - address assetAddr = allAssets[i]; - - if (IERC20(assetAddr).balanceOf(address(this)) >= outputs[i]) { - // Use Vault funds first if sufficient - IERC20(assetAddr).safeTransfer(msg.sender, outputs[i]); - } else { - address strategyAddr = assetDefaultStrategies[assetAddr]; - if (strategyAddr != address(0)) { - // Nothing in Vault, but something in Strategy, send from there - IStrategy strategy = IStrategy(strategyAddr); - strategy.withdraw(msg.sender, assetAddr, outputs[i]); - } else { - // Cant find funds anywhere - revert("Liquidity error"); - } - } + // transfer asset from the vault to the withdrawer + IERC20(asset).safeTransfer(msg.sender, amount); + + // Prevent insolvency + _postRedeem(amount.scaleBy(18, assetDecimals)); + } + + // slither-disable-end reentrancy-no-eth + /** + * @notice Claim a previously requested withdrawals once they are claimable. + * This requests can be claimed once the withdrawal queue's `claimable` amount + * is greater than or equal each request's `queued` amount and 10 minutes has passed. + * If one of the requests is not claimable, the whole transaction will revert with `Queue pending liquidity`. + * If one of the requests is not older than 10 minutes, + * the whole transaction will revert with `Claim delay not met`. + * @param _requestIds Unique ID of each withdrawal request + * @return amounts Amount of asset received for each request + * @return totalAmount Total amount of asset transferred to the withdrawer + */ + function claimWithdrawals(uint256[] calldata _requestIds) + external + virtual + whenNotCapitalPaused + nonReentrant + returns (uint256[] memory amounts, uint256 totalAmount) + { + // Add any asset to the withdrawal queue + // this needs to remain here as: + // - Vault can be funded and `addWithdrawalQueueLiquidity` is not externally called + // - funds can be withdrawn from a strategy + // + // Those funds need to be added to withdrawal queue liquidity + _addWithdrawalQueueLiquidity(); + + amounts = new uint256[](_requestIds.length); + for (uint256 i; i < _requestIds.length; ++i) { + // Scale all amounts to asset decimals, thus totalAmount is also in asset decimals + amounts[i] = _claimWithdrawal(_requestIds[i]); + totalAmount += amounts[i]; } - if (_minimumUnitAmount > 0) { - uint256 unitTotal = 0; - for (uint256 i = 0; i < outputs.length; ++i) { - unitTotal += _toUnits(outputs[i], allAssets[i]); - } - require( - unitTotal >= _minimumUnitAmount, - "Redeem amount lower than minimum" + // transfer all the claimed asset from the vault to the withdrawer + IERC20(asset).safeTransfer(msg.sender, totalAmount); + + // Prevent insolvency + _postRedeem(totalAmount.scaleBy(18, assetDecimals)); + + return (amounts, totalAmount); + } + + function _claimWithdrawal(uint256 requestId) + internal + returns (uint256 amount) + { + require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); + + // Load the structs from storage into memory + WithdrawalRequest memory request = withdrawalRequests[requestId]; + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; + + require( + request.timestamp + withdrawalClaimDelay <= block.timestamp, + "Claim delay not met" + ); + // If there isn't enough reserved liquidity in the queue to claim + require(request.queued <= queue.claimable, "Queue pending liquidity"); + require(request.withdrawer == msg.sender, "Not requester"); + require(request.claimed == false, "Already claimed"); + + // Store the request as claimed + withdrawalRequests[requestId].claimed = true; + // Store the updated claimed amount + withdrawalQueueMetadata.claimed = + queue.claimed + + SafeCast.toUint128( + StableMath.scaleBy(request.amount, assetDecimals, 18) ); - } - oUSD.burn(msg.sender, _amount); + emit WithdrawalClaimed(msg.sender, requestId, request.amount); - _postRedeem(_amount); + return StableMath.scaleBy(request.amount, assetDecimals, 18); } function _postRedeem(uint256 _amount) internal { - // Until we can prove that we won't affect the prices of our assets + // Until we can prove that we won't affect the prices of our asset // by withdrawing them, this should be here. // It's possible that a strategy was off on its asset total, perhaps // a reward token sold for more or for less than anticipated. @@ -225,15 +353,15 @@ contract VaultCore is VaultInitializer { totalUnits = _totalValue(); } - // Check that the OTokens are backed by enough assets + // Check that the OTokens are backed by enough asset if (maxSupplyDiff > 0) { - // If there are more outstanding withdrawal requests than assets in the vault and strategies - // then the available assets will be negative and totalUnits will be rounded up to zero. + // If there are more outstanding withdrawal requests than asset in the vault and strategies + // then the available asset will be negative and totalUnits will be rounded up to zero. // As we don't know the exact shortfall amount, we will reject all redeem and withdrawals require(totalUnits > 0, "Too many outstanding requests"); // Allow a max difference of maxSupplyDiff% between - // backing assets value and OUSD total supply + // asset value and OUSD total supply uint256 diff = oUSD.totalSupply().divPrecisely(totalUnits); require( (diff > 1e18 ? diff - 1e18 : 1e18 - diff) <= maxSupplyDiff, @@ -242,118 +370,55 @@ contract VaultCore is VaultInitializer { } } - /** - * @notice Burn OTokens for Metapool Strategy - * @param _amount Amount of OUSD to burn - * - * @dev Notice: can't use `nonReentrant` modifier since the `redeem` function could - * require withdrawal on `ConvexOUSDMetaStrategy` and that one can call `burnForStrategy` - * while the execution of the `redeem` has not yet completed -> causing a `nonReentrant` collision. - * - * Also important to understand is that this is a limitation imposed by the test suite. - * Production / mainnet contracts should never be configured in a way where mint/redeem functions - * that are moving funds between the Vault and end user wallets can influence strategies - * utilizing this function. - */ - function burnForStrategy(uint256 _amount) - external - virtual - whenNotCapitalPaused - onlyOusdMetaStrategy - { - require(_amount < MAX_INT, "Amount too high"); - - emit Redeem(msg.sender, _amount); - - // safe to cast because of the require check at the beginning of the function - netOusdMintedForStrategy -= int256(_amount); - - require( - abs(netOusdMintedForStrategy) < netOusdMintForStrategyThreshold, - "Attempting to burn too much OUSD." - ); - - // Burn OTokens - oUSD.burn(msg.sender, _amount); - } - /** * @notice Allocate unallocated funds on Vault to strategies. - **/ + */ function allocate() external virtual whenNotCapitalPaused nonReentrant { + // Add any unallocated asset to the withdrawal queue first + _addWithdrawalQueueLiquidity(); + _allocate(); } /** - * @dev Allocate unallocated funds on Vault to strategies. - **/ + * @dev Allocate asset (eg. WETH or USDC) to the default asset strategy + * if there is excess to the Vault buffer. + * This is called from either `mint` or `allocate` and assumes `_addWithdrawalQueueLiquidity` + * has been called before this function. + */ function _allocate() internal virtual { - uint256 vaultValue = _totalValueInVault(); - // Nothing in vault to allocate - if (vaultValue == 0) return; - uint256 strategiesValue = _totalValueInStrategies(); - // We have a method that does the same as this, gas optimisation - uint256 calculatedTotalValue = vaultValue + strategiesValue; - - // We want to maintain a buffer on the Vault so calculate a percentage - // modifier to multiply each amount being allocated by to enforce the - // vault buffer - uint256 vaultBufferModifier; - if (strategiesValue == 0) { - // Nothing in Strategies, allocate 100% minus the vault buffer to - // strategies - vaultBufferModifier = uint256(1e18) - vaultBuffer; - } else { - vaultBufferModifier = - (vaultBuffer * calculatedTotalValue) / - vaultValue; - if (1e18 > vaultBufferModifier) { - // E.g. 1e18 - (1e17 * 10e18)/5e18 = 8e17 - // (5e18 * 8e17) / 1e18 = 4e18 allocated from Vault - vaultBufferModifier = uint256(1e18) - vaultBufferModifier; - } else { - // We need to let the buffer fill - return; - } - } - if (vaultBufferModifier == 0) return; - - // Iterate over all assets in the Vault and allocate to the appropriate - // strategy - uint256 assetCount = allAssets.length; - for (uint256 i = 0; i < assetCount; ++i) { - IERC20 asset = IERC20(allAssets[i]); - uint256 assetBalance = asset.balanceOf(address(this)); - // No balance, nothing to do here - if (assetBalance == 0) continue; - - // Multiply the balance by the vault buffer modifier and truncate - // to the scale of the asset decimals - uint256 allocateAmount = assetBalance.mulTruncate( - vaultBufferModifier - ); + // No need to do anything if no default strategy for asset + address depositStrategyAddr = defaultStrategy; + if (depositStrategyAddr == address(0)) return; + + uint256 assetAvailableInVault = _assetAvailable(); + // No need to do anything if there isn't any asset in the vault to allocate + if (assetAvailableInVault == 0) return; + + // Calculate the target buffer for the vault using the total supply + uint256 totalSupply = oUSD.totalSupply(); + // Scaled to asset decimals + uint256 targetBuffer = totalSupply.mulTruncate(vaultBuffer).scaleBy( + assetDecimals, + 18 + ); - address depositStrategyAddr = assetDefaultStrategies[ - address(asset) - ]; - - if (depositStrategyAddr != address(0) && allocateAmount > 0) { - IStrategy strategy = IStrategy(depositStrategyAddr); - // Transfer asset to Strategy and call deposit method to - // mint or take required action - asset.safeTransfer(address(strategy), allocateAmount); - strategy.deposit(address(asset), allocateAmount); - emit AssetAllocated( - address(asset), - depositStrategyAddr, - allocateAmount - ); - } - } + // If available asset in the Vault is below or equal the target buffer then there's nothing to allocate + if (assetAvailableInVault <= targetBuffer) return; + + // The amount of asset to allocate to the default strategy + uint256 allocateAmount = assetAvailableInVault - targetBuffer; + + IStrategy strategy = IStrategy(depositStrategyAddr); + // Transfer asset to the strategy and call the strategy's deposit function + IERC20(asset).safeTransfer(address(strategy), allocateAmount); + strategy.deposit(asset, allocateAmount); + + emit AssetAllocated(asset, depositStrategyAddr, allocateAmount); } /** - * @notice Calculate the total value of assets held by the Vault and all + * @notice Calculate the total value of asset held by the Vault and all * strategies and update the supply of OTokens. */ function rebase() external virtual nonReentrant { @@ -361,7 +426,7 @@ contract VaultCore is VaultInitializer { } /** - * @dev Calculate the total value of assets held by the Vault and all + * @dev Calculate the total value of asset held by the Vault and all * strategies and update the supply of OTokens, optionally sending a * portion of the yield to the trustee. * @return totalUnits Total balance of Vault in units @@ -461,7 +526,7 @@ contract VaultCore is VaultInitializer { } /** - * @notice Determine the total value of assets held by the vault and its + * @notice Determine the total value of asset held by the vault and its * strategies. * @return value Total value in USD/ETH (1e18) */ @@ -470,16 +535,25 @@ contract VaultCore is VaultInitializer { } /** - * @dev Internal Calculate the total value of the assets held by the - * vault and its strategies. + * @dev Internal Calculate the total value of the asset held by the + * vault and its strategies. + * @dev The total value of all WETH held by the vault and all its strategies + * less any WETH that is reserved for the withdrawal queue. + * If there is not enough WETH in the vault and all strategies to cover + * all outstanding withdrawal requests then return a total value of 0. * @return value Total value in USD/ETH (1e18) */ function _totalValue() internal view virtual returns (uint256 value) { - return _totalValueInVault() + _totalValueInStrategies(); + // As asset is the only asset, just return the asset balance + value = _checkBalance(asset).scaleBy(18, assetDecimals); } /** - * @dev Internal to calculate total value of all assets held in Vault. + * @dev Internal to calculate total value of all asset held in Vault. + * @dev Only asset is supported in the OETH Vault so return the asset balance only + * Any ETH balances in the Vault will be ignored. + * Amounts from previously supported vault asset will also be ignored. + * For example, there is 1 wei left of stETH in the OETH Vault but is will be ignored. * @return value Total value in USD/ETH (1e18) */ function _totalValueInVault() @@ -488,48 +562,7 @@ contract VaultCore is VaultInitializer { virtual returns (uint256 value) { - uint256 assetCount = allAssets.length; - for (uint256 y; y < assetCount; ++y) { - address assetAddr = allAssets[y]; - uint256 balance = IERC20(assetAddr).balanceOf(address(this)); - if (balance > 0) { - value += _toUnits(balance, assetAddr); - } - } - } - - /** - * @dev Internal to calculate total value of all assets held in Strategies. - * @return value Total value in USD/ETH (1e18) - */ - function _totalValueInStrategies() internal view returns (uint256 value) { - uint256 stratCount = allStrategies.length; - for (uint256 i = 0; i < stratCount; ++i) { - value = value + _totalValueInStrategy(allStrategies[i]); - } - } - - /** - * @dev Internal to calculate total value of all assets held by strategy. - * @param _strategyAddr Address of the strategy - * @return value Total value in USD/ETH (1e18) - */ - function _totalValueInStrategy(address _strategyAddr) - internal - view - returns (uint256 value) - { - IStrategy strategy = IStrategy(_strategyAddr); - uint256 assetCount = allAssets.length; - for (uint256 y; y < assetCount; ++y) { - address assetAddr = allAssets[y]; - if (strategy.supportsAsset(assetAddr)) { - uint256 balance = strategy.checkBalance(assetAddr); - if (balance > 0) { - value += _toUnits(balance, assetAddr); - } - } - } + value = IERC20(asset).balanceOf(address(this)); } /** @@ -543,6 +576,15 @@ contract VaultCore is VaultInitializer { /** * @notice Get the balance of an asset held in Vault and all strategies. + * @dev Get the balance of an asset held in Vault and all strategies + * less any asset that is reserved for the withdrawal queue. + * BaseAsset is the only asset that can return a non-zero balance. + * All other asset will return 0 even if there is some dust amounts left in the Vault. + * For example, there is 1 wei left of stETH (or USDC) in the OETH (or OUSD) Vault but + * will return 0 in this function. + * + * If there is not enough asset in the vault and all strategies to cover all outstanding + * withdrawal requests then return a asset balance of 0 * @param _asset Address of asset * @return balance Balance of asset in decimals of asset */ @@ -552,6 +594,9 @@ contract VaultCore is VaultInitializer { virtual returns (uint256 balance) { + if (_asset != asset) return 0; + + // Get the asset in the vault and the strategies IERC20 asset = IERC20(_asset); balance = asset.balanceOf(address(this)); uint256 stratCount = allStrategies.length; @@ -561,272 +606,108 @@ contract VaultCore is VaultInitializer { balance = balance + strategy.checkBalance(_asset); } } + + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; + + // If the vault becomes insolvent enough that the total value in the vault and all strategies + // is less than the outstanding withdrawals. + // For example, there was a mass slashing event and most users request a withdrawal. + if (balance + queue.claimed < queue.queued) { + return 0; + } + + // Need to remove asset that is reserved for the withdrawal queue + return balance + queue.claimed - queue.queued; } /** - * @notice Calculate the outputs for a redeem function, i.e. the mix of - * coins that will be returned + * @notice Adds WETH to the withdrawal queue if there is a funding shortfall. + * @dev is called from the Native Staking strategy when validator withdrawals are processed. + * It also called before any WETH is allocated to a strategy. */ - function calculateRedeemOutputs(uint256 _amount) - external - view - returns (uint256[] memory) - { - return _calculateRedeemOutputs(_amount); + function addWithdrawalQueueLiquidity() external { + _addWithdrawalQueueLiquidity(); } /** - * @dev Calculate the outputs for a redeem function, i.e. the mix of - * coins that will be returned. - * @return outputs Array of amounts respective to the supported assets + * @dev Adds asset (eg. WETH or USDC) to the withdrawal queue if there is a funding shortfall. + * This assumes 1 asset equal 1 corresponding OToken. */ - function _calculateRedeemOutputs(uint256 _amount) + function _addWithdrawalQueueLiquidity() internal - view - virtual - returns (uint256[] memory outputs) + returns (uint256 addedClaimable) { - // We always give out coins in proportion to how many we have, - // Now if all coins were the same value, this math would easy, - // just take the percentage of each coin, and multiply by the - // value to be given out. But if coins are worth more than $1, - // then we would end up handing out too many coins. We need to - // adjust by the total value of coins. - // - // To do this, we total up the value of our coins, by their - // percentages. Then divide what we would otherwise give out by - // this number. - // - // Let say we have 100 DAI at $1.06 and 200 USDT at $1.00. - // So for every 1 DAI we give out, we'll be handing out 2 USDT - // Our total output ratio is: 33% * 1.06 + 66% * 1.00 = 1.02 - // - // So when calculating the output, we take the percentage of - // each coin, times the desired output value, divided by the - // totalOutputRatio. - // - // For example, withdrawing: 30 OUSD: - // DAI 33% * 30 / 1.02 = 9.80 DAI - // USDT = 66 % * 30 / 1.02 = 19.60 USDT - // - // Checking these numbers: - // 9.80 DAI * 1.06 = $10.40 - // 19.60 USDT * 1.00 = $19.60 - // - // And so the user gets $10.40 + $19.60 = $30 worth of value. + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - uint256 assetCount = allAssets.length; - uint256[] memory assetUnits = new uint256[](assetCount); - uint256[] memory assetBalances = new uint256[](assetCount); - outputs = new uint256[](assetCount); + // Check if the claimable asset is less than the queued amount + uint256 queueShortfall = queue.queued - queue.claimable; - // Calculate redeem fee - if (redeemFeeBps > 0) { - uint256 redeemFee = _amount.mulTruncateScale(redeemFeeBps, 1e4); - _amount = _amount - redeemFee; + // No need to do anything is the withdrawal queue is full funded + if (queueShortfall == 0) { + return 0; } - // Calculate assets balances and decimals once, - // for a large gas savings. - uint256 totalUnits = 0; - for (uint256 i = 0; i < assetCount; ++i) { - address assetAddr = allAssets[i]; - uint256 balance = _checkBalance(assetAddr); - assetBalances[i] = balance; - assetUnits[i] = _toUnits(balance, assetAddr); - totalUnits = totalUnits + assetUnits[i]; - } - // Calculate totalOutputRatio - uint256 totalOutputRatio = 0; - for (uint256 i = 0; i < assetCount; ++i) { - uint256 unitPrice = _toUnitPrice(allAssets[i], false); - uint256 ratio = (assetUnits[i] * unitPrice) / totalUnits; - totalOutputRatio = totalOutputRatio + ratio; - } - // Calculate final outputs - uint256 factor = _amount.divPrecisely(totalOutputRatio); - for (uint256 i = 0; i < assetCount; ++i) { - outputs[i] = (assetBalances[i] * factor) / totalUnits; - } - } + uint256 assetBalance = IERC20(asset).balanceOf(address(this)); - /*************************************** - Pricing - ****************************************/ + // Of the claimable withdrawal requests, how much is unclaimed? + // That is, the amount of asset that is currently allocated for the withdrawal queue + uint256 allocatedBaseAsset = queue.claimable - queue.claimed; - /** - * @notice Returns the total price in 18 digit units for a given asset. - * Never goes above 1, since that is how we price mints. - * @param asset address of the asset - * @return price uint256: unit (USD / ETH) price for 1 unit of the asset, in 18 decimal fixed - */ - function priceUnitMint(address asset) - external - view - returns (uint256 price) - { - /* need to supply 1 asset unit in asset's decimals and can not just hard-code - * to 1e18 and ignore calling `_toUnits` since we need to consider assets - * with the exchange rate - */ - uint256 units = _toUnits( - uint256(1e18).scaleBy(_getDecimals(asset), 18), - asset - ); - price = (_toUnitPrice(asset, true) * units) / 1e18; - } + // If there is no unallocated asset then there is nothing to add to the queue + if (assetBalance <= allocatedBaseAsset) { + return 0; + } - /** - * @notice Returns the total price in 18 digit unit for a given asset. - * Never goes below 1, since that is how we price redeems - * @param asset Address of the asset - * @return price uint256: unit (USD / ETH) price for 1 unit of the asset, in 18 decimal fixed - */ - function priceUnitRedeem(address asset) - external - view - returns (uint256 price) - { - /* need to supply 1 asset unit in asset's decimals and can not just hard-code - * to 1e18 and ignore calling `_toUnits` since we need to consider assets - * with the exchange rate - */ - uint256 units = _toUnits( - uint256(1e18).scaleBy(_getDecimals(asset), 18), - asset - ); - price = (_toUnitPrice(asset, false) * units) / 1e18; - } + uint256 unallocatedBaseAsset = assetBalance - allocatedBaseAsset; + // the new claimable amount is the smaller of the queue shortfall or unallocated asset + addedClaimable = queueShortfall < unallocatedBaseAsset + ? queueShortfall + : unallocatedBaseAsset; + uint256 newClaimable = queue.claimable + addedClaimable; - /*************************************** - Utils - ****************************************/ + // Store the new claimable amount back to storage + withdrawalQueueMetadata.claimable = SafeCast.toUint128(newClaimable); - /** - * @dev Convert a quantity of a token into 1e18 fixed decimal "units" - * in the underlying base (USD/ETH) used by the vault. - * Price is not taken into account, only quantity. - * - * Examples of this conversion: - * - * - 1e18 DAI becomes 1e18 units (same decimals) - * - 1e6 USDC becomes 1e18 units (decimal conversion) - * - 1e18 rETH becomes 1.2e18 units (exchange rate conversion) - * - * @param _raw Quantity of asset - * @param _asset Core Asset address - * @return value 1e18 normalized quantity of units - */ - function _toUnits(uint256 _raw, address _asset) - internal - view - returns (uint256) - { - UnitConversion conversion = assets[_asset].unitConversion; - if (conversion == UnitConversion.DECIMALS) { - return _raw.scaleBy(18, _getDecimals(_asset)); - } else if (conversion == UnitConversion.GETEXCHANGERATE) { - uint256 exchangeRate = IGetExchangeRateToken(_asset) - .getExchangeRate(); - return (_raw * exchangeRate) / 1e18; - } else { - revert("Unsupported conversion type"); - } + // emit a WithdrawalClaimable event + emit WithdrawalClaimable(newClaimable, addedClaimable); } /** - * @dev Returns asset's unit price accounting for different asset types - * and takes into account the context in which that price exists - - * - mint or redeem. - * - * Note: since we are returning the price of the unit and not the one of the - * asset (see comment above how 1 rETH exchanges for 1.2 units) we need - * to make the Oracle price adjustment as well since we are pricing the - * units and not the assets. - * - * The price also snaps to a "full unit price" in case a mint or redeem - * action would be unfavourable to the protocol. - * + * @dev Calculate how much asset (eg. WETH or USDC) in the vault is not reserved for the withdrawal queue. + * That is, it is available to be redeemed or deposited into a strategy. */ - function _toUnitPrice(address _asset, bool isMint) - internal - view - returns (uint256 price) - { - UnitConversion conversion = assets[_asset].unitConversion; - price = IOracle(priceProvider).price(_asset); - - if (conversion == UnitConversion.GETEXCHANGERATE) { - uint256 exchangeRate = IGetExchangeRateToken(_asset) - .getExchangeRate(); - price = (price * 1e18) / exchangeRate; - } else if (conversion != UnitConversion.DECIMALS) { - revert("Unsupported conversion type"); - } + function _assetAvailable() internal view returns (uint256 assetAvailable) { + WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; - /* At this stage the price is already adjusted to the unit - * so the price checks are agnostic to underlying asset being - * pegged to a USD or to an ETH or having a custom exchange rate. - */ - require(price <= MAX_UNIT_PRICE_DRIFT, "Vault: Price exceeds max"); - require(price >= MIN_UNIT_PRICE_DRIFT, "Vault: Price under min"); - - if (isMint) { - /* Never price a normalized unit price for more than one - * unit of OETH/OUSD when minting. - */ - if (price > 1e18) { - price = 1e18; - } - require(price >= MINT_MINIMUM_UNIT_PRICE, "Asset price below peg"); - } else { - /* Never give out more than 1 normalized unit amount of assets - * for one unit of OETH/OUSD when redeeming. - */ - if (price < 1e18) { - price = 1e18; - } - } - } + // The amount of asset that is still to be claimed in the withdrawal queue + uint256 outstandingWithdrawals = queue.queued - queue.claimed; - /** - * @dev Get the number of decimals of a token asset - * @param _asset Address of the asset - * @return decimals number of decimals - */ - function _getDecimals(address _asset) - internal - view - returns (uint256 decimals) - { - decimals = assets[_asset].decimals; - require(decimals > 0, "Decimals not cached"); - } + // The amount of sitting in asset in the vault + uint256 assetBalance = IERC20(asset).balanceOf(address(this)); + // If there is not enough asset in the vault to cover the outstanding withdrawals + if (assetBalance <= outstandingWithdrawals) return 0; - /** - * @notice Return the number of assets supported by the Vault. - */ - function getAssetCount() public view returns (uint256) { - return allAssets.length; + return assetBalance - outstandingWithdrawals; } + /*************************************** + Utils + ****************************************/ + /** - * @notice Gets the vault configuration of a supported asset. - * @param _asset Address of the token asset + * @notice Return the number of asset supported by the Vault. */ - function getAssetConfig(address _asset) - public - view - returns (Asset memory config) - { - config = assets[_asset]; + function getAssetCount() public view returns (uint256) { + return 1; } /** * @notice Return all vault asset addresses in order */ function getAllAssets() external view returns (address[] memory) { - return allAssets; + address[] memory a = new address[](1); + a[0] = asset; + return a; } /** @@ -849,59 +730,7 @@ contract VaultCore is VaultInitializer { * @return true if supported */ function isSupportedAsset(address _asset) external view returns (bool) { - return assets[_asset].isSupported; - } - - function ADMIN_IMPLEMENTATION() external view returns (address adminImpl) { - bytes32 slot = adminImplPosition; - // solhint-disable-next-line no-inline-assembly - assembly { - adminImpl := sload(slot) - } - } - - /** - * @dev Falldown to the admin implementation - * @notice This is a catch all for all functions not declared in core - */ - // solhint-disable-next-line no-complex-fallback - fallback() external { - bytes32 slot = adminImplPosition; - // solhint-disable-next-line no-inline-assembly - assembly { - // Copy msg.data. We take full control of memory in this inline assembly - // block because it will not return to Solidity code. We overwrite the - // Solidity scratch pad at memory position 0. - calldatacopy(0, 0, calldatasize()) - - // Call the implementation. - // out and outsize are 0 because we don't know the size yet. - let result := delegatecall( - gas(), - sload(slot), - 0, - calldatasize(), - 0, - 0 - ) - - // Copy the returned data. - returndatacopy(0, 0, returndatasize()) - - switch result - // delegatecall returns 0 on error. - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } - - function abs(int256 x) private pure returns (uint256) { - require(x < int256(MAX_INT), "Amount too high"); - return x >= 0 ? uint256(x) : uint256(-x); + return asset == _asset; } function _min(uint256 a, uint256 b) internal pure returns (uint256) { diff --git a/contracts/contracts/vault/VaultInitializer.sol b/contracts/contracts/vault/VaultInitializer.sol index 692a3d1e83..e1463a3eb9 100644 --- a/contracts/contracts/vault/VaultInitializer.sol +++ b/contracts/contracts/vault/VaultInitializer.sol @@ -9,24 +9,17 @@ pragma solidity ^0.8.0; import "./VaultStorage.sol"; -contract VaultInitializer is VaultStorage { - function initialize(address _priceProvider, address _oToken) - external - onlyGovernor - initializer - { - require(_priceProvider != address(0), "PriceProvider address is zero"); +abstract contract VaultInitializer is VaultStorage { + constructor(address _asset) VaultStorage(_asset) {} + + function initialize(address _oToken) external onlyGovernor initializer { require(_oToken != address(0), "oToken address is zero"); oUSD = OUSD(_oToken); - priceProvider = _priceProvider; - rebasePaused = false; capitalPaused = true; - // Initial redeem fee of 0 basis points - redeemFeeBps = 0; // Initial Vault buffer of 0% vaultBuffer = 0; // Initial allocate threshold of 25,000 OUSD @@ -35,7 +28,7 @@ contract VaultInitializer is VaultStorage { rebaseThreshold = 1000e18; // Initialize all strategies allStrategies = new address[](0); - // Start with drip duration disabled - dripDuration = 1; + // Start with drip duration: 7 days + dripDuration = 604800; } } diff --git a/contracts/contracts/vault/VaultStorage.sol b/contracts/contracts/vault/VaultStorage.sol index 53ef34a936..00fc068449 100644 --- a/contracts/contracts/vault/VaultStorage.sol +++ b/contracts/contracts/vault/VaultStorage.sol @@ -12,17 +12,15 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { IStrategy } from "../interfaces/IStrategy.sol"; +import { IWETH9 } from "../interfaces/IWETH9.sol"; import { Governable } from "../governance/Governable.sol"; import { OUSD } from "../token/OUSD.sol"; import { Initializable } from "../utils/Initializable.sol"; import "../utils/Helpers.sol"; -contract VaultStorage is Initializable, Governable { +abstract contract VaultStorage is Initializable, Governable { using SafeERC20 for IERC20; - event AssetSupported(address _asset); - event AssetRemoved(address _asset); - event AssetDefaultStrategyUpdated(address _asset, address _strategy); event AssetAllocated(address _asset, address _strategy, uint256 _amount); event StrategyApproved(address _addr); event StrategyRemoved(address _addr); @@ -30,12 +28,10 @@ contract VaultStorage is Initializable, Governable { event Redeem(address _addr, uint256 _value); event CapitalPaused(); event CapitalUnpaused(); + event DefaultStrategyUpdated(address _strategy); event RebasePaused(); event RebaseUnpaused(); event VaultBufferUpdated(uint256 _vaultBuffer); - event OusdMetaStrategyUpdated(address _ousdMetaStrategy); - event RedeemFeeUpdated(uint256 _redeemFeeBps); - event PriceProviderUpdated(address _priceProvider); event AllocateThresholdUpdated(uint256 _threshold); event RebaseThresholdUpdated(uint256 _threshold); event StrategistUpdated(address _address); @@ -43,16 +39,6 @@ contract VaultStorage is Initializable, Governable { event YieldDistribution(address _to, uint256 _yield, uint256 _fee); event TrusteeFeeBpsChanged(uint256 _basis); event TrusteeAddressChanged(address _address); - event NetOusdMintForStrategyThresholdChanged(uint256 _threshold); - event SwapperChanged(address _address); - event SwapAllowedUndervalueChanged(uint256 _basis); - event SwapSlippageChanged(address _asset, uint256 _basis); - event Swapped( - address indexed _fromAsset, - address indexed _toAsset, - uint256 _fromAssetAmount, - uint256 _toAssetAmount - ); event StrategyAddedToMintWhitelist(address indexed strategy); event StrategyRemovedFromMintWhitelist(address indexed strategy); event RebasePerSecondMaxChanged(uint256 rebaseRatePerSecond); @@ -77,27 +63,10 @@ contract VaultStorage is Initializable, Governable { // slither-disable-start uninitialized-state // slither-disable-start constable-states - // Assets supported by the Vault, i.e. Stablecoins - enum UnitConversion { - DECIMALS, - GETEXCHANGERATE - } - // Changed to fit into a single storage slot so the decimals needs to be recached - struct Asset { - // Note: OETHVaultCore doesn't use `isSupported` when minting, - // redeeming or checking balance of assets. - bool isSupported; - UnitConversion unitConversion; - uint8 decimals; - // Max allowed slippage from the Oracle price when swapping collateral assets in basis points. - // For example 40 == 0.4% slippage - uint16 allowedOracleSlippageBps; - } - /// @dev mapping of supported vault assets to their configuration - mapping(address => Asset) internal assets; + uint256 private _deprecated_assets; /// @dev list of all assets supported by the vault. - address[] internal allAssets; + address[] private _deprecated_allAssets; // Strategies approved for use by the Vault struct Strategy { @@ -110,14 +79,14 @@ contract VaultStorage is Initializable, Governable { address[] internal allStrategies; /// @notice Address of the Oracle price provider contract - address public priceProvider; + address private _deprecated_priceProvider; /// @notice pause rebasing if true bool public rebasePaused; /// @notice pause operations that change the OToken supply. /// eg mint, redeem, allocate, mint/burn for strategy bool public capitalPaused; /// @notice Redemption fee in basis points. eg 50 = 0.5% - uint256 public redeemFeeBps; + uint256 private _deprecated_redeemFeeBps; /// @notice Percentage of assets to keep in Vault to handle (most) withdrawals. 100% = 1e18. uint256 public vaultBuffer; /// @notice OToken mints over this amount automatically allocate funds. 18 decimals. @@ -128,11 +97,6 @@ contract VaultStorage is Initializable, Governable { /// @dev Address of the OToken token. eg OUSD or OETH. OUSD public oUSD; - /// @dev Storage slot for the address of the VaultAdmin contract that is delegated to - // keccak256("OUSD.vault.governor.admin.impl"); - bytes32 public constant adminImplPosition = - 0xa2bd3d3cf188a41358c8b401076eb59066b09dec5775650c0de4c55187d17bd9; - /// @dev Address of the contract responsible for post rebase syncs with AMMs address private _deprecated_rebaseHooksAddr = address(0); @@ -144,7 +108,7 @@ contract VaultStorage is Initializable, Governable { /// @notice Mapping of asset address to the Strategy that they should automatically // be allocated to - mapping(address => address) public assetDefaultStrategies; + uint256 private _deprecated_assetDefaultStrategies; /// @notice Max difference between total supply and total value of assets. 18 decimals. uint256 public maxSupplyDiff; @@ -158,31 +122,17 @@ contract VaultStorage is Initializable, Governable { /// @dev Deprecated: Tokens that should be swapped for stablecoins address[] private _deprecated_swapTokens; - uint256 constant MINT_MINIMUM_UNIT_PRICE = 0.998e18; - /// @notice Metapool strategy that is allowed to mint/burn OTokens without changing collateral - address public ousdMetaStrategy; + address private _deprecated_ousdMetaStrategy; /// @notice How much OTokens are currently minted by the strategy - int256 public netOusdMintedForStrategy; + int256 private _deprecated_netOusdMintedForStrategy; /// @notice How much net total OTokens are allowed to be minted by all strategies - uint256 public netOusdMintForStrategyThreshold; - - uint256 constant MIN_UNIT_PRICE_DRIFT = 0.7e18; - uint256 constant MAX_UNIT_PRICE_DRIFT = 1.3e18; - - /// @notice Collateral swap configuration. - /// @dev is packed into a single storage slot to save gas. - struct SwapConfig { - // Contract that swaps the vault's collateral assets - address swapper; - // Max allowed percentage the total value can drop below the total supply in basis points. - // For example 100 == 1% - uint16 allowedUndervalueBps; - } - SwapConfig internal swapConfig = SwapConfig(address(0), 0); + uint256 private _deprecated_netOusdMintForStrategyThreshold; + + uint256 private _deprecated_swapConfig; // List of strategies that can mint oTokens directly // Used in OETHBaseVaultCore @@ -249,25 +199,27 @@ contract VaultStorage is Initializable, Governable { uint256 internal constant MAX_REBASE_PER_SECOND = uint256(0.05 ether) / 1 days; + /// @notice Default strategy for asset + address public defaultStrategy; + // For future use - uint256[43] private __gap; + uint256[42] private __gap; + + /// @notice Index of WETH asset in allAssets array + /// Legacy OETHVaultCore code, relocated here for vault consistency. + uint256 private _deprecated_wethAssetIndex; + + /// @dev Address of the asset (eg. WETH or USDC) + address public immutable asset; + uint8 internal immutable assetDecimals; // slither-disable-end constable-states // slither-disable-end uninitialized-state - /** - * @notice set the implementation for the admin, this needs to be in a base class else we cannot set it - * @param newImpl address of the implementation - */ - function setAdminImpl(address newImpl) external onlyGovernor { - require( - Address.isContract(newImpl), - "new implementation is not a contract" - ); - bytes32 position = adminImplPosition; - // solhint-disable-next-line no-inline-assembly - assembly { - sstore(position, newImpl) - } + constructor(address _asset) { + uint8 _decimals = IWETH9(_asset).decimals(); + require(_decimals <= 18, "invalid asset decimals"); + asset = _asset; + assetDecimals = _decimals; } } diff --git a/contracts/contracts/vault/AbstractOTokenZapper.sol b/contracts/contracts/zapper/AbstractOTokenZapper.sol similarity index 100% rename from contracts/contracts/vault/AbstractOTokenZapper.sol rename to contracts/contracts/zapper/AbstractOTokenZapper.sol diff --git a/contracts/contracts/vault/OETHBaseZapper.sol b/contracts/contracts/zapper/OETHBaseZapper.sol similarity index 100% rename from contracts/contracts/vault/OETHBaseZapper.sol rename to contracts/contracts/zapper/OETHBaseZapper.sol diff --git a/contracts/contracts/vault/OETHZapper.sol b/contracts/contracts/zapper/OETHZapper.sol similarity index 100% rename from contracts/contracts/vault/OETHZapper.sol rename to contracts/contracts/zapper/OETHZapper.sol diff --git a/contracts/contracts/vault/OSonicZapper.sol b/contracts/contracts/zapper/OSonicZapper.sol similarity index 100% rename from contracts/contracts/vault/OSonicZapper.sol rename to contracts/contracts/zapper/OSonicZapper.sol diff --git a/contracts/deploy/base/000_mock.js b/contracts/deploy/base/000_mock.js index d06adc7ab5..9691f51043 100644 --- a/contracts/deploy/base/000_mock.js +++ b/contracts/deploy/base/000_mock.js @@ -22,11 +22,11 @@ const deployWOETH = async () => { // Initialize the proxy // prettier-ignore await cWOETHProxy["initialize(address,address,bytes)"]( - cWOETHImpl.address, - governorAddr, - "0x", - await getTxOpts() - ); + cWOETHImpl.address, + governorAddr, + "0x", + await getTxOpts() + ); // Initialize implementation const cWOETH = await ethers.getContractAt( @@ -83,11 +83,7 @@ const deployCore = async () => { const dwOETHb = await deployWithConfirmation("WOETHBase", [ cOETHbProxy.address, // Base token ]); - const dOETHbVault = await deployWithConfirmation("OETHVault"); - const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ - cWETH.address, - ]); - const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVaultAdmin", [ + const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVault", [ cWETH.address, ]); @@ -98,7 +94,6 @@ const deployCore = async () => { "IVault", cOETHbVaultProxy.address ); - const cOracleRouter = await ethers.getContract("MockOracleRouter"); // Init OETHb const resolution = ethers.utils.parseUnits("1", 27); @@ -119,16 +114,15 @@ const deployCore = async () => { // Init OETHbVault const initDataOETHbVault = cOETHbVault.interface.encodeFunctionData( - "initialize(address,address)", + "initialize(address)", [ - cOracleRouter.address, // OracleRouter cOETHbProxy.address, // OETHb ] ); // prettier-ignore await cOETHbVaultProxy .connect(sDeployer)["initialize(address,address,bytes)"]( - dOETHbVault.address, + dOETHbVaultAdmin.address, governorAddr, initDataOETHbVault ); @@ -146,10 +140,8 @@ const deployCore = async () => { initDatawOETHb ) - await cOETHbVaultProxy.connect(sGovernor).upgradeTo(dOETHbVaultCore.address); - await cOETHbVault.connect(sGovernor).setAdminImpl(dOETHbVaultAdmin.address); + await cOETHbVaultProxy.connect(sGovernor).upgradeTo(dOETHbVaultAdmin.address); - await cOETHbVault.connect(sGovernor).supportAsset(cWETH.address, 0); await cOETHbVault.connect(sGovernor).unpauseCapital(); }; @@ -171,11 +163,13 @@ const deployBridgedWOETHStrategy = async () => { await deployWithConfirmation("BridgedWOETHStrategyProxy"); const cStrategyProxy = await ethers.getContract("BridgedWOETHStrategyProxy"); + const cOracleRouter = await ethers.getContract("MockOracleRouter"); const dStrategyImpl = await deployWithConfirmation("BridgedWOETHStrategy", [ [addresses.zero, cOETHbVaultProxy.address], cWETH.address, cWOETHProxy.address, cOETHbProxy.address, + cOracleRouter.address, ]); const cStrategy = await ethers.getContractAt( "BridgedWOETHStrategy", diff --git a/contracts/deploy/base/040_vault_upgrade.js b/contracts/deploy/base/040_vault_upgrade.js new file mode 100644 index 0000000000..b44242adb2 --- /dev/null +++ b/contracts/deploy/base/040_vault_upgrade.js @@ -0,0 +1,79 @@ +const { deployOnBase } = require("../../utils/deploy-l2"); +const { deployWithConfirmation } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnBase( + { + deployName: "040_vault_upgrade", + //proposalId: "", + }, + async ({ ethers }) => { + // 1. Deploy OETHBaseVault implementations + const dOETHbVault = await deployWithConfirmation( + "OETHBaseVault", + [addresses.base.WETH], + "OETHBaseVault", + true + ); + + // 2. Connect to the OETHBase Vault as its governor via the proxy + const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); + console.log("OETHb Vault Proxy Address:", cOETHbVaultProxy.address); + const cOETHbVault = await ethers.getContractAt( + "IVault", + cOETHbVaultProxy.address + ); + + // 3. Connect to the Aerodrome AMO + const defaultStrategy = await ethers.getContract( + "AerodromeAMOStrategyProxy" + ); + + // 4. Connect to Bridged WOETH Strategy + const bridgedWOETHStrategy = await ethers.getContract( + "BridgedWOETHStrategyProxy" + ); + + // 5. Connect to oracle router + const cOracleRouter = await ethers.getContract("OETHBaseOracleRouter"); + + // 6. Connect to OETHb Proxy + const cOETHbtProxy = await ethers.getContract("OETHBaseProxy"); + + // 7. Deploy new Bridged WOETH Strategy implementation (with oracle as immutable) + const dStrategyImpl = await deployWithConfirmation("BridgedWOETHStrategy", [ + [addresses.zero, cOETHbVaultProxy.address], + addresses.base.WETH, + addresses.base.BridgedWOETH, + cOETHbtProxy.address, + cOracleRouter.address, + ]); + + // ---------------- + // Governance Actions + // ---------------- + return { + name: "Upgrade OETHBaseVault to new single Vault implementations", + actions: [ + // 1. Upgrade Bridged WOETH Strategy implementation + { + contract: bridgedWOETHStrategy, + signature: "upgradeTo(address)", + args: [dStrategyImpl.address], + }, + // 2. Upgrade OETHBaseVaultProxy to new implementation + { + contract: cOETHbVaultProxy, + signature: "upgradeTo(address)", + args: [dOETHbVault.address], + }, + // 3. Set Aerodrome AMO as default strategy + { + contract: cOETHbVault, + signature: "setDefaultStrategy(address)", + args: [defaultStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/deploy/deployActions.js b/contracts/deploy/deployActions.js index 94692f1a98..175d4b4f44 100644 --- a/contracts/deploy/deployActions.js +++ b/contracts/deploy/deployActions.js @@ -283,7 +283,6 @@ const deployConvexOUSDMetaStrategy = async () => { * Configure Vault by adding supported assets and Strategies. */ const configureVault = async () => { - const assetAddresses = await getAssetAddresses(deployments); const { governorAddr, strategistAddr } = await getNamedAccounts(); // Signers const sGovernor = await ethers.provider.getSigner(governorAddr); @@ -294,19 +293,6 @@ const configureVault = async () => { await ethers.getContract("VaultProxy") ).address ); - // Set up supported assets for Vault - await withConfirmation( - cVault.connect(sGovernor).supportAsset(assetAddresses.USDS, 0) - ); - log("Added USDS asset to Vault"); - await withConfirmation( - cVault.connect(sGovernor).supportAsset(assetAddresses.USDT, 0) - ); - log("Added USDT asset to Vault"); - await withConfirmation( - cVault.connect(sGovernor).supportAsset(assetAddresses.USDC, 0) - ); - log("Added USDC asset to Vault"); // Unpause deposits await withConfirmation(cVault.connect(sGovernor).unpauseCapital()); log("Unpaused deposits on Vault"); @@ -314,13 +300,17 @@ const configureVault = async () => { await withConfirmation( cVault.connect(sGovernor).setStrategistAddr(strategistAddr) ); + + // Set withdrawal claim delay to 10m + await withConfirmation( + cVault.connect(sGovernor).setWithdrawalClaimDelay(10 * 60) + ); }; /** * Configure OETH Vault by adding supported assets and Strategies. */ -const configureOETHVault = async (isSimpleOETH) => { - const assetAddresses = await getAssetAddresses(deployments); +const configureOETHVault = async () => { let { governorAddr, deployerAddr, strategistAddr } = await getNamedAccounts(); // Signers let sGovernor = await ethers.provider.getSigner(governorAddr); @@ -338,14 +328,6 @@ const configureOETHVault = async (isSimpleOETH) => { sGovernor = sDeployer; } - // Set up supported assets for Vault - const { WETH, RETH, stETH, frxETH } = assetAddresses; - const assets = isSimpleOETH ? [WETH] : [WETH, RETH, stETH, frxETH]; - for (const asset of assets) { - await withConfirmation(cVault.connect(sGovernor).supportAsset(asset, 0)); - } - log("Added assets to OETH Vault"); - // Unpause deposits await withConfirmation(cVault.connect(sGovernor).unpauseCapital()); log("Unpaused deposits on OETH Vault"); @@ -354,12 +336,6 @@ const configureOETHVault = async (isSimpleOETH) => { cVault.connect(sGovernor).setStrategistAddr(strategistAddr) ); - // Cache WETH asset address - await withConfirmation(cVault.connect(sGovernor).cacheWETHAssetIndex()); - - // Redeem fee to 0 - await withConfirmation(cVault.connect(sGovernor).setRedeemFeeBps(0)); - // Allocate threshold await withConfirmation( cVault @@ -508,29 +484,47 @@ const configureStrategies = async (harvesterProxy, oethHarvesterProxy) => { // Signers const sGovernor = await ethers.provider.getSigner(governorAddr); - const compoundProxy = await ethers.getContract("CompoundStrategyProxy"); - const compound = await ethers.getContractAt( - "CompoundStrategy", - compoundProxy.address - ); - await withConfirmation( - compound.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); + // Configure Compound Strategy if deployed + const compoundDeployment = await hre.deployments + .get("CompoundStrategyProxy") + .catch(() => null); + if (compoundDeployment) { + const compound = await ethers.getContractAt( + "CompoundStrategy", + compoundDeployment.address + ); + await withConfirmation( + compound.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) + ); + } - const aaveProxy = await ethers.getContract("AaveStrategyProxy"); - const aave = await ethers.getContractAt("AaveStrategy", aaveProxy.address); - await withConfirmation( - aave.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); + // Configure Aave Strategy if deployed + const aaveDeployment = await hre.deployments + .get("AaveStrategyProxy") + .catch(() => null); + if (aaveDeployment) { + const aave = await ethers.getContractAt( + "AaveStrategy", + aaveDeployment.address + ); + await withConfirmation( + aave.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) + ); + } - const convexProxy = await ethers.getContract("ConvexStrategyProxy"); - const convex = await ethers.getContractAt( - "ConvexStrategy", - convexProxy.address - ); - await withConfirmation( - convex.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) - ); + // Configure Convex Strategy if deployed + const convexDeployment = await hre.deployments + .get("ConvexStrategyProxy") + .catch(() => null); + if (convexDeployment) { + const convex = await ethers.getContractAt( + "ConvexStrategy", + convexDeployment.address + ); + await withConfirmation( + convex.connect(sGovernor).setHarvesterAddress(harvesterProxy.address) + ); + } const nativeStakingSSVStrategyProxy = await ethers.getContract( "NativeStakingSSVStrategyProxy" @@ -1080,11 +1074,7 @@ const deployOETHCore = async () => { // Main contracts const dOETH = await deployWithConfirmation("OETH"); - const dOETHVault = await deployWithConfirmation("OETHVault"); - const dOETHVaultCore = await deployWithConfirmation("OETHVaultCore", [ - assetAddresses.WETH, - ]); - const dOETHVaultAdmin = await deployWithConfirmation("OETHVaultAdmin", [ + const dOETHVault = await deployWithConfirmation("OETHVault", [ assetAddresses.WETH, ]); @@ -1092,10 +1082,6 @@ const deployOETHCore = async () => { const cOETHProxy = await ethers.getContract("OETHProxy"); const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy"); const cOETH = await ethers.getContractAt("OETH", cOETHProxy.address); - - const oracleRouterContractName = - isMainnet || isHoodiOrFork ? "OETHOracleRouter" : "OracleRouter"; - const cOETHOracleRouter = await ethers.getContract(oracleRouterContractName); const cOETHVault = await ethers.getContractAt( "IVault", cOETHVaultProxy.address @@ -1125,24 +1111,15 @@ const deployOETHCore = async () => { await withConfirmation( cOETHVault .connect(sGovernor) - .initialize( - cOETHOracleRouter.address, - cOETHProxy.address, - await getTxOpts() - ) + .initialize(cOETHProxy.address, await getTxOpts()) ); log("Initialized OETHVault"); await withConfirmation( - cOETHVaultProxy.connect(sGovernor).upgradeTo(dOETHVaultCore.address) + cOETHVaultProxy.connect(sGovernor).upgradeTo(dOETHVault.address) ); log("Upgraded VaultCore implementation"); - await withConfirmation( - cOETHVault.connect(sGovernor).setAdminImpl(dOETHVaultAdmin.address) - ); - - log("Initialized VaultAdmin implementation"); // Initialize OETH /* Set the original resolution to 27 decimals. We used to have it set to 18 * decimals at launch and then migrated to 27. Having it set to 27 it will @@ -1167,16 +1144,19 @@ const deployOETHCore = async () => { }; const deployOUSDCore = async () => { - const { governorAddr } = await hre.getNamedAccounts(); + const { governorAddr, deployerAddr } = await hre.getNamedAccounts(); + const assetAddresses = await getAssetAddresses(deployments); log(`Using asset addresses: ${JSON.stringify(assetAddresses, null, 2)}`); // Signers const sGovernor = await ethers.provider.getSigner(governorAddr); + const sDeployer = await ethers.provider.getSigner(deployerAddr); // Proxies await deployWithConfirmation("OUSDProxy"); await deployWithConfirmation("VaultProxy"); + log("Deployed OUSD Token and OUSD Vault proxies"); // Main contracts let dOUSD; @@ -1185,17 +1165,20 @@ const deployOUSDCore = async () => { } else { dOUSD = await deployWithConfirmation("OUSD"); } - const dVault = await deployWithConfirmation("Vault"); - const dVaultCore = await deployWithConfirmation("VaultCore"); - const dVaultAdmin = await deployWithConfirmation("VaultAdmin"); + + // Deploy Vault implementations + const dVaultAdmin = await deployWithConfirmation("OUSDVault", [ + assetAddresses.USDC, + ]); + log("Deployed OUSD Vault implementations (Core, Admin)"); // Get contract instances const cOUSDProxy = await ethers.getContract("OUSDProxy"); const cVaultProxy = await ethers.getContract("VaultProxy"); const cOUSD = await ethers.getContractAt("OUSD", cOUSDProxy.address); - const cOracleRouter = await ethers.getContract("OracleRouter"); const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); + // Initialize OUSD Token Proxy await withConfirmation( cOUSDProxy["initialize(address,address,bytes)"]( dOUSD.address, @@ -1203,34 +1186,25 @@ const deployOUSDCore = async () => { [] ) ); - log("Initialized OUSDProxy"); + log("Initialized OUSD Token Proxy"); - // Need to call the initializer on the Vault then upgraded it to the actual - // VaultCore implementation + // Initialize OUSD Vault Proxy with Vault Core implementation + // prettier-ignore await withConfirmation( - cVaultProxy["initialize(address,address,bytes)"]( - dVault.address, + cVaultProxy.connect(sDeployer)["initialize(address,address,bytes)"]( + dVaultAdmin.address, governorAddr, - [] + [], + await getTxOpts() ) ); + log("Initialized OUSD Vault Proxy"); + // Initialize OUSD Vault Core await withConfirmation( - cVault - .connect(sGovernor) - .initialize(cOracleRouter.address, cOUSDProxy.address) - ); - log("Initialized Vault"); - - await withConfirmation( - cVaultProxy.connect(sGovernor).upgradeTo(dVaultCore.address) + cVault.connect(sGovernor).initialize(cOUSDProxy.address) ); - log("Upgraded VaultCore implementation"); - - await withConfirmation( - cVault.connect(sGovernor).setAdminImpl(dVaultAdmin.address) - ); - log("Initialized VaultAdmin implementation"); + log("Initialized OUSD Vault Core"); // Initialize OUSD /* Set the original resolution to 27 decimals. We used to have it set to 18 @@ -1252,7 +1226,7 @@ const deployOUSDCore = async () => { await withConfirmation( cOUSD.connect(sGovernor).initialize(cVaultProxy.address, resolution) ); - log("Initialized OUSD"); + log("Initialized OUSD Token"); await withConfirmation( cVault @@ -1486,17 +1460,11 @@ const deployWOeth = async () => { }; const deployOETHSwapper = async () => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); + const { deployerAddr } = await getNamedAccounts(); const sDeployer = await ethers.provider.getSigner(deployerAddr); - const sGovernor = await ethers.provider.getSigner(governorAddr); const assetAddresses = await getAssetAddresses(deployments); - const vaultProxy = await ethers.getContract("OETHVaultProxy"); - const vault = await ethers.getContractAt("IVault", vaultProxy.address); - - const mockSwapper = await ethers.getContract("MockSwapper"); - await deployWithConfirmation("Swapper1InchV5"); const cSwapper = await ethers.getContract("Swapper1InchV5"); @@ -1508,27 +1476,13 @@ const deployOETHSwapper = async () => { assetAddresses.WETH, assetAddresses.frxETH, ]); - - await vault.connect(sGovernor).setSwapper(mockSwapper.address); - await vault.connect(sGovernor).setSwapAllowedUndervalue(100); - - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.RETH, 200); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.stETH, 70); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.WETH, 20); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.frxETH, 20); }; const deployOUSDSwapper = async () => { - const { deployerAddr, governorAddr } = await getNamedAccounts(); + const { deployerAddr } = await getNamedAccounts(); const sDeployer = await ethers.provider.getSigner(deployerAddr); - const sGovernor = await ethers.provider.getSigner(governorAddr); const assetAddresses = await getAssetAddresses(deployments); - - const vaultProxy = await ethers.getContract("VaultProxy"); - const vault = await ethers.getContractAt("IVault", vaultProxy.address); - - const mockSwapper = await ethers.getContract("MockSwapper"); // Assumes deployOETHSwapper has already been run const cSwapper = await ethers.getContract("Swapper1InchV5"); @@ -1539,13 +1493,6 @@ const deployOUSDSwapper = async () => { assetAddresses.USDC, assetAddresses.USDT, ]); - - await vault.connect(sGovernor).setSwapper(mockSwapper.address); - await vault.connect(sGovernor).setSwapAllowedUndervalue(100); - - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.USDS, 50); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.USDC, 50); - await vault.connect(sGovernor).setOracleSlippage(assetAddresses.USDT, 50); }; const deployBaseAerodromeAMOStrategyImplementation = async () => { diff --git a/contracts/deploy/mainnet/000_mock.js b/contracts/deploy/mainnet/000_mock.js index f2a598e705..e1eb11be80 100644 --- a/contracts/deploy/mainnet/000_mock.js +++ b/contracts/deploy/mainnet/000_mock.js @@ -133,6 +133,7 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { // Deploy a mock Vault with additional functions for tests await deploy("MockVault", { + args: [(await ethers.getContract("MockUSDC")).address], from: governorAddr, }); diff --git a/contracts/deploy/mainnet/001_core.js b/contracts/deploy/mainnet/001_core.js index 024146b8b9..cd2cec69e5 100644 --- a/contracts/deploy/mainnet/001_core.js +++ b/contracts/deploy/mainnet/001_core.js @@ -39,7 +39,7 @@ const main = async () => { oethDripper ); await configureVault(); - await configureOETHVault(false); + await configureOETHVault(); await configureStrategies(harvesterProxy, oethHarvesterProxy); await deployBuyback(); await deployUniswapV3Pool(); diff --git a/contracts/deploy/mainnet/167_ousd_vault_upgrade.js b/contracts/deploy/mainnet/167_ousd_vault_upgrade.js new file mode 100644 index 0000000000..62c39a76fe --- /dev/null +++ b/contracts/deploy/mainnet/167_ousd_vault_upgrade.js @@ -0,0 +1,58 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "167_ousd_vault_upgrade", + forceDeploy: false, + //forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ deployWithConfirmation }) => { + // Deployer Actions + // ---------------- + + // 1. Deploy new OUSD Vault Core and Admin implementations + const dVault = await deployWithConfirmation("OUSDVault", [ + addresses.mainnet.USDC, + ]); + + // 2. Connect to the OUSD Vault as its governor via the proxy + const cVaultProxy = await ethers.getContract("VaultProxy"); + const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); + + // 3. Connect to the Morpho OUSD v2 Strategy Proxy + const cOUSDAMO = await ethers.getContract("OUSDCurveAMOProxy"); + const cMorphoStrategy = await ethers.getContract( + "OUSDMorphoV2StrategyProxy" + ); + + // Governance Actions + // ---------------- + return { + name: "Upgrade OUSD Vault to new Core and Admin implementations", + actions: [ + // 1. Upgrade the OUSD Vault proxy to the new core vault implementation + { + contract: cVaultProxy, + signature: "upgradeTo(address)", + args: [dVault.address], + }, + // 2. Add OUSD/USDC AMO to mint whitelist + { + contract: cVault, + signature: "addStrategyToMintWhitelist(address)", + args: [cOUSDAMO.address], + }, + // 3. Set Morpho OUSD v2 Strategy as default strategy + { + contract: cVault, + signature: "setDefaultStrategy(address)", + args: [cMorphoStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/deploy/mainnet/168_oeth_vault_upgrade.js b/contracts/deploy/mainnet/168_oeth_vault_upgrade.js new file mode 100644 index 0000000000..b6c3a18ef0 --- /dev/null +++ b/contracts/deploy/mainnet/168_oeth_vault_upgrade.js @@ -0,0 +1,54 @@ +const addresses = require("../../utils/addresses"); +const { deploymentWithGovernanceProposal } = require("../../utils/deploy"); + +module.exports = deploymentWithGovernanceProposal( + { + deployName: "168_oeth_vault_upgrade", + forceDeploy: false, + //forceSkip: true, + reduceQueueTime: true, + deployerIsProposer: false, + proposalId: "", + }, + async ({ deployWithConfirmation }) => { + // Deployer Actions + // ---------------- + + // 1. Deploy new OETH Vault Core and Admin implementations + const dVaultAdmin = await deployWithConfirmation( + "OETHVault", + [addresses.mainnet.WETH], + undefined, + true + ); + + // 2. Connect to the OETH Vault as its governor via the proxy + const cVaultProxy = await ethers.getContract("OETHVaultProxy"); + const cVault = await ethers.getContractAt("IVault", cVaultProxy.address); + + // 3. Connect to the Compounding Staking Strategy Proxy to set it as default strategy + const defaultStrategy = await ethers.getContract( + "CompoundingStakingSSVStrategyProxy" + ); + + // Governance Actions + // ---------------- + return { + name: "Upgrade OETH Vault to new Core and Admin implementations", + actions: [ + // 1. Upgrade the OETH Vault proxy to the new core vault implementation + { + contract: cVaultProxy, + signature: "upgradeTo(address)", + args: [dVaultAdmin.address], + }, + // 2. Set OETH/WETH AMO as default strategy + { + contract: cVault, + signature: "setDefaultStrategy(address)", + args: [defaultStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/deploy/plume/002_core.js b/contracts/deploy/plume/002_core.js index ba6eae2b9a..c090140a6c 100644 --- a/contracts/deploy/plume/002_core.js +++ b/contracts/deploy/plume/002_core.js @@ -53,11 +53,10 @@ module.exports = deployOnPlume( const dOETHpVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ addresses.plume.WETH, ]); - const dOETHpVaultAdmin = await deployWithConfirmation( - "OETHBaseVaultAdmin", - [addresses.plume.WETH] - ); - console.log("OETHBaseVaultAdmin deployed at", dOETHpVaultAdmin.address); + const dOETHpVaultAdmin = await deployWithConfirmation("OETHBaseVault", [ + addresses.plume.WETH, + ]); + console.log("OETHBaseVault deployed at", dOETHpVaultAdmin.address); // Get contract instances const cOETHp = await ethers.getContractAt("OETHPlume", cOETHpProxy.address); const cwOETHp = await ethers.getContractAt( diff --git a/contracts/deploy/sonic/000_mock.js b/contracts/deploy/sonic/000_mock.js index 72f53dde49..ea325b8a41 100644 --- a/contracts/deploy/sonic/000_mock.js +++ b/contracts/deploy/sonic/000_mock.js @@ -46,13 +46,8 @@ const deployCore = async () => { const dWOSonic = await deployWithConfirmation("WOSonic", [ cOSonicProxy.address, // Base token ]); - const dOSonicVaultCore = await deployWithConfirmation("OSonicVaultCore", [ - cWS.address, - ]); - const dOSonicVaultAdmin = await deployWithConfirmation("OSonicVaultAdmin", [ - cWS.address, - ]); + const dOSonicVault = await deployWithConfirmation("OSVault", [cWS.address]); // Get contract instances const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); @@ -61,7 +56,6 @@ const deployCore = async () => { "IVault", cOSonicVaultProxy.address ); - const cOracleRouter = await ethers.getContract("MockOracleRouter"); // Init OSonic const resolution = ethers.utils.parseUnits("1", 27); @@ -82,16 +76,15 @@ const deployCore = async () => { // Init OSonicVault const initDataOSonicVault = cOSonicVault.interface.encodeFunctionData( - "initialize(address,address)", + "initialize(address)", [ - cOracleRouter.address, // OracleRouter cOSonicProxy.address, // OSonic ] ); // prettier-ignore await cOSonicVaultProxy .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonicVaultCore.address, + dOSonicVault.address, governorAddr, initDataOSonicVault ); @@ -109,12 +102,8 @@ const deployCore = async () => { initDataWOSonic ) - await cOSonicVaultProxy - .connect(sGovernor) - .upgradeTo(dOSonicVaultCore.address); - await cOSonicVault.connect(sGovernor).setAdminImpl(dOSonicVaultAdmin.address); + await cOSonicVaultProxy.connect(sGovernor).upgradeTo(dOSonicVault.address); - await cOSonicVault.connect(sGovernor).supportAsset(cWS.address, 0); await cOSonicVault.connect(sGovernor).unpauseCapital(); // Set withdrawal claim delay to 1 day await cOSonicVault.connect(sGovernor).setWithdrawalClaimDelay(86400); @@ -163,13 +152,13 @@ const deployStakingStrategy = async () => { cSonicStakingStrategy.interface.encodeFunctionData("initialize()", []); // prettier-ignore await withConfirmation( - cSonicStakingStrategyProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dSonicStakingStrategy.address, - governorAddr, - initSonicStakingStrategy - ) - ); + cSonicStakingStrategyProxy + .connect(sDeployer)["initialize(address,address,bytes)"]( + dSonicStakingStrategy.address, + governorAddr, + initSonicStakingStrategy + ) + ); }; const deployDripper = async () => { @@ -191,12 +180,12 @@ const deployDripper = async () => { // prettier-ignore await withConfirmation( cOSonicDripperProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dFixedRateDripper.address, - governorAddr, - "0x" - ) - ); + .connect(sDeployer)["initialize(address,address,bytes)"]( + dFixedRateDripper.address, + governorAddr, + "0x" + ) + ); }; const main = async () => { diff --git a/contracts/deploy/sonic/001_vault_and_token.js b/contracts/deploy/sonic/001_vault_and_token.js index 0ecb106f98..73b4ec9c81 100644 --- a/contracts/deploy/sonic/001_vault_and_token.js +++ b/contracts/deploy/sonic/001_vault_and_token.js @@ -49,10 +49,10 @@ module.exports = deployOnSonic( ]); console.log(`Deployed Vault Core to ${dOSonicVaultCore.address}`); - const dOSonicVaultAdmin = await deployWithConfirmation("OSonicVaultAdmin", [ + const dOSonicVault = await deployWithConfirmation("OSonicVault", [ cWS.address, ]); - console.log(`Deployed Vault Admin to ${dOSonicVaultAdmin.address}`); + console.log(`Deployed Vault Admin to ${dOSonicVault.address}`); // Get contract instances const cOSonic = await ethers.getContractAt("OSonic", cOSonicProxy.address); @@ -77,11 +77,11 @@ module.exports = deployOnSonic( // prettier-ignore await cOSonicProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonic.address, - addresses.sonic.timelock, - initDataOSonic - ); + .connect(sDeployer)["initialize(address,address,bytes)"]( + dOSonic.address, + addresses.sonic.timelock, + initDataOSonic + ); console.log("Initialized Origin S"); // Init OSonicVault @@ -94,11 +94,11 @@ module.exports = deployOnSonic( ); // prettier-ignore await cOSonicVaultProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dOSonicVaultCore.address, - addresses.sonic.timelock, - initDataOSonicVault - ); + .connect(sDeployer)["initialize(address,address,bytes)"]( + dOSonicVaultCore.address, + addresses.sonic.timelock, + initDataOSonicVault + ); console.log("Initialized Origin S Vault"); // Init WOSonic @@ -108,11 +108,11 @@ module.exports = deployOnSonic( ); // prettier-ignore await cWOSonicProxy - .connect(sDeployer)["initialize(address,address,bytes)"]( - dWOSonic.address, - addresses.sonic.timelock, - initDataWOSonic - ) + .connect(sDeployer)["initialize(address,address,bytes)"]( + dWOSonic.address, + addresses.sonic.timelock, + initDataWOSonic + ) console.log("Initialized Wrapper Origin S"); // Deploy the Dripper @@ -133,7 +133,7 @@ module.exports = deployOnSonic( // Init the Dripper proxy // prettier-ignore await withConfirmation( - cOSonicDripperProxy + cOSonicDripperProxy .connect(sDeployer)["initialize(address,address,bytes)"]( dFixedRateDripper.address, addresses.sonic.timelock, @@ -170,7 +170,7 @@ module.exports = deployOnSonic( { contract: cOSonicVault, signature: "setAdminImpl(address)", - args: [dOSonicVaultAdmin.address], + args: [dOSonicVault.address], }, // 3. Support wrapped S { diff --git a/contracts/deploy/sonic/026_vault_upgrade.js b/contracts/deploy/sonic/026_vault_upgrade.js new file mode 100644 index 0000000000..30725e2e3a --- /dev/null +++ b/contracts/deploy/sonic/026_vault_upgrade.js @@ -0,0 +1,56 @@ +const { deployOnSonic } = require("../../utils/deploy-l2"); +const { deployWithConfirmation } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnSonic( + { + deployName: "026_vault_upgrade", + //proposalId: "", + }, + async ({ ethers }) => { + // 1. Deploy new OSonicVault implementations + const dOSonicVault = await deployWithConfirmation( + "OSVault", + [addresses.sonic.wS], + "OSVault", + true + ); + + // 2. Connect to the OSonic Vault as its governor via the proxy + const cOSonicVaultProxy = await ethers.getContract("OSonicVaultProxy"); + const cOSonicVault = await ethers.getContractAt( + "IVault", + cOSonicVaultProxy.address + ); + + // 3. Connect to Sonic Staking Strategy + const sonicStakingStrategy = await ethers.getContract( + "SonicStakingStrategyProxy" + ); + console.log( + "Sonic Staking Strategy Address:", + sonicStakingStrategy.address + ); + + // ---------------- + // Governance Actions + // ---------------- + return { + name: "Upgrade OSonicVault to new single Vault implementations", + actions: [ + // 1. Upgrade OSonicVaultProxy to new implementation + { + contract: cOSonicVaultProxy, + signature: "upgradeTo(address)", + args: [dOSonicVault.address], + }, + // 2. Set Sonic Staking Strategy as default strategy + { + contract: cOSonicVault, + signature: "setDefaultStrategy(address)", + args: [sonicStakingStrategy.address], + }, + ], + }; + } +); diff --git a/contracts/docs/OETHBaseVaultAdminSquashed.svg b/contracts/docs/OETHBaseVaultAdminSquashed.svg deleted file mode 100644 index a4f23c6a14..0000000000 --- a/contracts/docs/OETHBaseVaultAdminSquashed.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -UmlClassDiagram - - - -282 - -OETHBaseVaultAdmin -../contracts/vault/OETHBaseVaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultAdmin>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(address, address, uint256, uint256, bytes): uint256 <<OETHVaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<OETHVaultAdmin>> -    _withdrawAllFromStrategies() <<OETHVaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<VaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_weth: address) <<OETHBaseVaultAdmin>> - - - diff --git a/contracts/docs/OETHBaseVaultCoreSquashed.svg b/contracts/docs/OETHBaseVaultCoreSquashed.svg deleted file mode 100644 index 194fb658f8..0000000000 --- a/contracts/docs/OETHBaseVaultCoreSquashed.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - -UmlClassDiagram - - - -283 - -OETHBaseVaultCore -../contracts/vault/OETHBaseVaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -   __gap: uint256[50] <<OETHVaultCore>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultCore>> -   wethAssetIndex: uint256 <<OETHVaultCore>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<OETHVaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<OETHBaseVaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<OETHVaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _totalValue(): (value: uint256) <<OETHVaultCore>> -    _totalValueInVault(): (value: uint256) <<OETHVaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<OETHVaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<OETHVaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _claimWithdrawal(requestId: uint256): (amount: uint256) <<OETHVaultCore>> -    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<OETHVaultCore>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    null() <<VaultCore>> -    cacheWETHAssetIndex() <<onlyGovernor>> <<OETHVaultCore>> -    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    addWithdrawalQueueLiquidity() <<OETHVaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> -    constructor(_weth: address) <<OETHBaseVaultCore>> - - - diff --git a/contracts/docs/OETHBaseVaultStorage.svg b/contracts/docs/OETHBaseVaultStorage.svg deleted file mode 100644 index f768dd006b..0000000000 --- a/contracts/docs/OETHBaseVaultStorage.svg +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - -StorageDiagram - - - -9 - -OETHBaseVaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79-122 - -123 - -124-173 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint256[44]: VaultStorage.__gap (1408) - -uint256: OETHVaultCore.wethAssetIndex (32) - -uint256[50]: OETHVaultCore.__gap (1600) - - - -1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) - - - -9:8->1 - - - - - -2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -9:10->2 - - - - - -3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) - - - -9:13->3 - - - - - -4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -9:15->4 - - - - - -5 - -address[]: _deprecated_swapTokens <<Array>> -0x9b22d3d61959b4d3528b1d8ba932c96fbe302b36a1aad1d95cab54f9e0a135ea - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -9:32->5 - - - - - -6 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) - - - -9:38->6 - - - - - -7 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -9:45->7 - - - - - -8 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -9:51->8 - - - - - diff --git a/contracts/docs/OETHVaultAdminSquashed.svg b/contracts/docs/OETHVaultAdminSquashed.svg deleted file mode 100644 index 42ba2da7ee..0000000000 --- a/contracts/docs/OETHVaultAdminSquashed.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -UmlClassDiagram - - - -286 - -OETHVaultAdmin -../contracts/vault/OETHVaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultAdmin>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(address, address, uint256, uint256, bytes): uint256 <<OETHVaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<OETHVaultAdmin>> -    _withdrawAllFromStrategies() <<OETHVaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<VaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_weth: address) <<OETHVaultAdmin>> - - - diff --git a/contracts/docs/OETHVaultCoreSquashed.svg b/contracts/docs/OETHVaultCoreSquashed.svg deleted file mode 100644 index 4bb8488671..0000000000 --- a/contracts/docs/OETHVaultCoreSquashed.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - -UmlClassDiagram - - - -287 - -OETHVaultCore -../contracts/vault/OETHVaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -   __gap: uint256[50] <<OETHVaultCore>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultCore>> -   wethAssetIndex: uint256 <<OETHVaultCore>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<OETHVaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<OETHVaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<OETHVaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _totalValue(): (value: uint256) <<OETHVaultCore>> -    _totalValueInVault(): (value: uint256) <<OETHVaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<OETHVaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<OETHVaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _claimWithdrawal(requestId: uint256): (amount: uint256) <<OETHVaultCore>> -    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<OETHVaultCore>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    null() <<VaultCore>> -    cacheWETHAssetIndex() <<onlyGovernor>> <<OETHVaultCore>> -    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    addWithdrawalQueueLiquidity() <<OETHVaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> -    constructor(_weth: address) <<OETHVaultCore>> - - - diff --git a/contracts/docs/OETHVaultStorage.svg b/contracts/docs/OETHVaultStorage.svg deleted file mode 100644 index e425f8712d..0000000000 --- a/contracts/docs/OETHVaultStorage.svg +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - -StorageDiagram - - - -8 - -OETHVaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79-122 - -123 - -124-173 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint256[44]: VaultStorage.__gap (1408) - -uint256: wethAssetIndex (32) - -uint256[50]: __gap (1600) - - - -1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) - - - -8:8->1 - - - - - -2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:10->2 - - - - - -3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) - - - -8:13->3 - - - - - -4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:15->4 - - - - - -5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) - - - -8:37->5 - - - - - -6 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -8:44->6 - - - - - -7 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -8:50->7 - - - - - diff --git a/contracts/docs/OSonicVaultAdminSquashed.svg b/contracts/docs/OSonicVaultAdminSquashed.svg deleted file mode 100644 index ba51485b4e..0000000000 --- a/contracts/docs/OSonicVaultAdminSquashed.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -UmlClassDiagram - - - -289 - -OSonicVaultAdmin -../contracts/vault/OSonicVaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultAdmin>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(address, address, uint256, uint256, bytes): uint256 <<OETHVaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<OETHVaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<OETHVaultAdmin>> -    _withdrawAllFromStrategies() <<OETHVaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<OSonicVaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<OETHVaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    constructor(_wS: address) <<OSonicVaultAdmin>> - - - diff --git a/contracts/docs/OSonicVaultCoreSquashed.svg b/contracts/docs/OSonicVaultCoreSquashed.svg deleted file mode 100644 index 9d467a3367..0000000000 --- a/contracts/docs/OSonicVaultCoreSquashed.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - -UmlClassDiagram - - - -290 - -OSonicVaultCore -../contracts/vault/OSonicVaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[44] <<VaultStorage>> -   __gap: uint256[50] <<OETHVaultCore>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   weth: address <<OETHVaultCore>> -   wethAssetIndex: uint256 <<OETHVaultCore>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<OETHVaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<OETHVaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<OETHVaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _totalValue(): (value: uint256) <<OETHVaultCore>> -    _totalValueInVault(): (value: uint256) <<OETHVaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<OETHVaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<OETHVaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _claimWithdrawal(requestId: uint256): (amount: uint256) <<OETHVaultCore>> -    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<OETHVaultCore>> -    _wethAvailable(): (wethAvailable: uint256) <<OETHVaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeem(uint256, uint256) <<OSonicVaultCore>> -    burnForStrategy(amount: uint256) <<whenNotCapitalPaused>> <<OETHVaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    null() <<VaultCore>> -    cacheWETHAssetIndex() <<onlyGovernor>> <<OETHVaultCore>> -    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<OETHVaultCore>> -    addWithdrawalQueueLiquidity() <<OETHVaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> -    constructor(_wS: address) <<OSonicVaultCore>> - - - diff --git a/contracts/docs/OSonicVaultStorage.svg b/contracts/docs/OSonicVaultStorage.svg deleted file mode 100644 index 1f9243ad89..0000000000 --- a/contracts/docs/OSonicVaultStorage.svg +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - -StorageDiagram - - - -8 - -OSonicVaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79-122 - -123 - -124-173 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint256[44]: VaultStorage.__gap (1408) - -uint256: OETHVaultCore.wethAssetIndex (32) - -uint256[50]: OETHVaultCore.__gap (1600) - - - -1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) - - - -8:8->1 - - - - - -2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:10->2 - - - - - -3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) - - - -8:13->3 - - - - - -4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) - - - -8:15->4 - - - - - -5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) - - - -8:37->5 - - - - - -6 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -8:44->6 - - - - - -7 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -8:50->7 - - - - - diff --git a/contracts/docs/VaultCoreSquashed.svg b/contracts/docs/VaultCoreSquashed.svg deleted file mode 100644 index e0accdf3c5..0000000000 --- a/contracts/docs/VaultCoreSquashed.svg +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - -UmlClassDiagram - - - -302 - -VaultCore -../contracts/vault/VaultCore.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[43] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> -   MAX_REBASE: uint256 <<VaultStorage>> -   MAX_REBASE_PER_SECOND: uint256 <<VaultStorage>> -   MAX_INT: uint256 <<VaultCore>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   priceProvider: address <<VaultStorage>> -   rebasePaused: bool <<VaultStorage>> -   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   lastRebase: uint64 <<VaultStorage>> -   dripDuration: uint64 <<VaultStorage>> -   rebasePerSecondMax: uint64 <<VaultStorage>> -   rebasePerSecondTarget: uint64 <<VaultStorage>> - -Private: -    abs(x: int256): uint256 <<VaultCore>> -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<VaultCore>> -    _redeem(_amount: uint256, _minimumUnitAmount: uint256) <<VaultCore>> -    _postRedeem(_amount: uint256) <<VaultCore>> -    _allocate() <<VaultCore>> -    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> -    _nextYield(supply: uint256, vaultValue: uint256, nonRebasing: uint256): (yield: uint256, targetRate: uint256) <<VaultCore>> -    _totalValue(): (value: uint256) <<VaultCore>> -    _totalValueInVault(): (value: uint256) <<VaultCore>> -    _totalValueInStrategies(): (value: uint256) <<VaultCore>> -    _totalValueInStrategy(_strategyAddr: address): (value: uint256) <<VaultCore>> -    _checkBalance(_asset: address): (balance: uint256) <<VaultCore>> -    _calculateRedeemOutputs(_amount: uint256): (outputs: uint256[]) <<VaultCore>> -    _toUnits(_raw: uint256, _asset: address): uint256 <<VaultCore>> -    _toUnitPrice(_asset: address, isMint: bool): (price: uint256) <<VaultCore>> -    _getDecimals(_asset: address): (decimals: uint256) <<VaultCore>> -    _min(a: uint256, b: uint256): uint256 <<VaultCore>> -    _max(a: uint256, b: uint256): uint256 <<VaultCore>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    initialize(_priceProvider: address, _oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> -    mint(_asset: address, _amount: uint256, _minimumOusdAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> -    redeem(_amount: uint256, _minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused, onlyOusdMetaStrategy>> <<VaultCore>> -    redeemAll(_minimumUnitAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    allocate() <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> -    rebase() <<nonReentrant>> <<VaultCore>> -    previewYield(): (yield: uint256) <<VaultCore>> -    totalValue(): (value: uint256) <<VaultCore>> -    checkBalance(_asset: address): uint256 <<VaultCore>> -    calculateRedeemOutputs(_amount: uint256): uint256[] <<VaultCore>> -    priceUnitMint(asset: address): (price: uint256) <<VaultCore>> -    priceUnitRedeem(asset: address): (price: uint256) <<VaultCore>> -    getAllAssets(): address[] <<VaultCore>> -    getStrategyCount(): uint256 <<VaultCore>> -    getAllStrategies(): address[] <<VaultCore>> -    isSupportedAsset(_asset: address): bool <<VaultCore>> -    ADMIN_IMPLEMENTATION(): (adminImpl: address) <<VaultCore>> -    null() <<VaultCore>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> RebasePerSecondMaxChanged(rebaseRatePerSecond: uint256) <<VaultStorage>> -    <<event>> DripDurationChanged(dripDuration: uint256) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotRebasePaused() <<VaultCore>> -    <<modifier>> whenNotCapitalPaused() <<VaultCore>> -    <<modifier>> onlyOusdMetaStrategy() <<VaultCore>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    getAssetCount(): uint256 <<VaultCore>> -    getAssetConfig(_asset: address): (config: Asset) <<VaultCore>> - - - diff --git a/contracts/docs/VaultHierarchy.svg b/contracts/docs/VaultHierarchy.svg index 2954412d6e..d7d69bf0d9 100644 --- a/contracts/docs/VaultHierarchy.svg +++ b/contracts/docs/VaultHierarchy.svg @@ -4,180 +4,159 @@ - - + + UmlClassDiagram - - + + -21 - -Governable -../contracts/governance/Governable.sol +44 + +<<Abstract>> +Governable +../contracts/governance/Governable.sol - + -261 - -OUSD -../contracts/token/OUSD.sol +307 + +OUSD +../contracts/token/OUSD.sol - + -261->21 - - +307->44 + + - + -277 - -<<Abstract>> -Initializable -../contracts/utils/Initializable.sol +324 + +<<Abstract>> +Initializable +../contracts/utils/Initializable.sol - + -282 - -OETHBaseVaultAdmin -../contracts/vault/OETHBaseVaultAdmin.sol +330 + +OETHBaseVault +../contracts/vault/OETHBaseVault.sol - - -286 - -OETHVaultAdmin -../contracts/vault/OETHVaultAdmin.sol + + +335 + +<<Abstract>> +VaultAdmin +../contracts/vault/VaultAdmin.sol - + -282->286 - - +330->335 + + - + -283 - -OETHBaseVaultCore -../contracts/vault/OETHBaseVaultCore.sol +332 + +OETHVault +../contracts/vault/OETHVault.sol - - -287 - -OETHVaultCore -../contracts/vault/OETHVaultCore.sol - - + -283->287 - - +332->335 + + - - -293 - -VaultAdmin -../contracts/vault/VaultAdmin.sol + + +333 + +OSonicVault +../contracts/vault/OSonicVault.sol - + -286->293 - - +333->335 + + - - -294 - -VaultCore -../contracts/vault/VaultCore.sol + + +334 + +OUSDVault +../contracts/vault/OUSDVault.sol - + -287->294 - - +334->335 + + - - -289 - -OSonicVaultAdmin -../contracts/vault/OSonicVaultAdmin.sol + + +336 + +<<Abstract>> +VaultCore +../contracts/vault/VaultCore.sol - + -289->286 - - +335->336 + + - - -290 - -OSonicVaultCore -../contracts/vault/OSonicVaultCore.sol + + +337 + +<<Abstract>> +VaultInitializer +../contracts/vault/VaultInitializer.sol - + -290->287 - - - - - -296 - -VaultStorage -../contracts/vault/VaultStorage.sol - - +336->337 + + + + + +338 + +<<Abstract>> +VaultStorage +../contracts/vault/VaultStorage.sol + + -293->296 - - - - - -295 - -VaultInitializer -../contracts/vault/VaultInitializer.sol - - - -294->295 - - +337->338 + + - + -295->296 - - - - - -296->21 - - - - - -296->261 - - - - +338->44 + + + + -296->277 - - +338->307 + + + + + +338->324 + + diff --git a/contracts/docs/VaultAdminSquashed.svg b/contracts/docs/VaultSquashed.svg similarity index 70% rename from contracts/docs/VaultAdminSquashed.svg rename to contracts/docs/VaultSquashed.svg index 221eefa703..06204eb0e2 100644 --- a/contracts/docs/VaultAdminSquashed.svg +++ b/contracts/docs/VaultSquashed.svg @@ -4,169 +4,177 @@ - - + + UmlClassDiagram - - + + -301 - -VaultAdmin -../contracts/vault/VaultAdmin.sol - -Private: -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _deprecated_rebaseHooksAddr: address <<VaultStorage>> -   _deprecated_uniswapAddr: address <<VaultStorage>> -   _deprecated_swapTokens: address[] <<VaultStorage>> -   __gap: uint256[43] <<VaultStorage>> -Internal: -   assets: mapping(address=>Asset) <<VaultStorage>> -   allAssets: address[] <<VaultStorage>> -   allStrategies: address[] <<VaultStorage>> -   swapConfig: SwapConfig <<VaultStorage>> +334 + +OUSDVault +../contracts/vault/OUSDVault.sol + +Private: +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _deprecated_assets: uint256 <<VaultStorage>> +   _deprecated_allAssets: address[] <<VaultStorage>> +   _deprecated_priceProvider: address <<VaultStorage>> +   _deprecated_redeemFeeBps: uint256 <<VaultStorage>> +   _deprecated_rebaseHooksAddr: address <<VaultStorage>> +   _deprecated_uniswapAddr: address <<VaultStorage>> +   _deprecated_assetDefaultStrategies: uint256 <<VaultStorage>> +   _deprecated_swapTokens: address[] <<VaultStorage>> +   _deprecated_ousdMetaStrategy: address <<VaultStorage>> +   _deprecated_netOusdMintedForStrategy: int256 <<VaultStorage>> +   _deprecated_netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> +   _deprecated_swapConfig: uint256 <<VaultStorage>> +   _deprecated_dripper: address <<VaultStorage>> +   __gap: uint256[42] <<VaultStorage>> +   _deprecated_wethAssetIndex: uint256 <<VaultStorage>> +Internal: +   allStrategies: address[] <<VaultStorage>>   MAX_REBASE: uint256 <<VaultStorage>>   MAX_REBASE_PER_SECOND: uint256 <<VaultStorage>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   strategies: mapping(address=>Strategy) <<VaultStorage>> -   priceProvider: address <<VaultStorage>> +   assetDecimals: uint8 <<VaultStorage>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   strategies: mapping(address=>Strategy) <<VaultStorage>>   rebasePaused: bool <<VaultStorage>>   capitalPaused: bool <<VaultStorage>> -   redeemFeeBps: uint256 <<VaultStorage>> -   vaultBuffer: uint256 <<VaultStorage>> -   autoAllocateThreshold: uint256 <<VaultStorage>> -   rebaseThreshold: uint256 <<VaultStorage>> -   oUSD: OUSD <<VaultStorage>> -   adminImplPosition: bytes32 <<VaultStorage>> -   strategistAddr: address <<VaultStorage>> -   assetDefaultStrategies: mapping(address=>address) <<VaultStorage>> -   maxSupplyDiff: uint256 <<VaultStorage>> -   trusteeAddress: address <<VaultStorage>> -   trusteeFeeBps: uint256 <<VaultStorage>> -   MINT_MINIMUM_UNIT_PRICE: uint256 <<VaultStorage>> -   ousdMetaStrategy: address <<VaultStorage>> -   netOusdMintedForStrategy: int256 <<VaultStorage>> -   netOusdMintForStrategyThreshold: uint256 <<VaultStorage>> -   MIN_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   MAX_UNIT_PRICE_DRIFT: uint256 <<VaultStorage>> -   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> -   dripper: address <<VaultStorage>> -   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> -   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> -   withdrawalClaimDelay: uint256 <<VaultStorage>> -   lastRebase: uint64 <<VaultStorage>> -   dripDuration: uint64 <<VaultStorage>> -   rebasePerSecondMax: uint64 <<VaultStorage>> -   rebasePerSecondTarget: uint64 <<VaultStorage>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<VaultAdmin>> -    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> -    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> -    _withdrawAllFromStrategy(_strategyAddr: address) <<VaultAdmin>> -    _withdrawAllFromStrategies() <<VaultAdmin>> -    _cacheDecimals(token: address) <<VaultAdmin>> -External: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setAdminImpl(newImpl: address) <<onlyGovernor>> <<VaultStorage>> -    setPriceProvider(_priceProvider: address) <<onlyGovernor>> <<VaultAdmin>> -    setRedeemFeeBps(_redeemFeeBps: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setAssetDefaultStrategy(_asset: address, _strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setNetOusdMintForStrategyThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setDripper(_dripper: address) <<onlyGovernor>> <<VaultAdmin>> -    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setRebaseRateMax(yearlyApr: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    setDripDuration(_dripDuration: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    swapCollateral(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _minToAssetAmount: uint256, _data: bytes): (toAssetAmount: uint256) <<nonReentrant, onlyGovernorOrStrategist>> <<VaultAdmin>> -    setSwapper(_swapperAddr: address) <<onlyGovernor>> <<VaultAdmin>> -    swapper(): (swapper_: address) <<VaultAdmin>> -    setSwapAllowedUndervalue(_basis: uint16) <<onlyGovernor>> <<VaultAdmin>> -    allowedSwapUndervalue(): (value: uint256) <<VaultAdmin>> -    setOracleSlippage(_asset: address, _allowedOracleSlippageBps: uint16) <<onlyGovernor>> <<VaultAdmin>> -    supportAsset(_asset: address, _unitConversion: uint8) <<onlyGovernor>> <<VaultAdmin>> -    removeAsset(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    cacheDecimals(_asset: address) <<onlyGovernor>> <<VaultAdmin>> -    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> -    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> -    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> -    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> -    setOusdMetaStrategy(_ousdMetaStrategy: address) <<onlyGovernor>> <<VaultAdmin>> -    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> -    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> -    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> AssetSupported(_asset: address) <<VaultStorage>> -    <<event>> AssetRemoved(_asset: address) <<VaultStorage>> -    <<event>> AssetDefaultStrategyUpdated(_asset: address, _strategy: address) <<VaultStorage>> -    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> -    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> -    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> -    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> -    <<event>> CapitalPaused() <<VaultStorage>> -    <<event>> CapitalUnpaused() <<VaultStorage>> -    <<event>> RebasePaused() <<VaultStorage>> -    <<event>> RebaseUnpaused() <<VaultStorage>> -    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> -    <<event>> OusdMetaStrategyUpdated(_ousdMetaStrategy: address) <<VaultStorage>> -    <<event>> RedeemFeeUpdated(_redeemFeeBps: uint256) <<VaultStorage>> -    <<event>> PriceProviderUpdated(_priceProvider: address) <<VaultStorage>> -    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> -    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> -    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> -    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> -    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> -    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> -    <<event>> NetOusdMintForStrategyThresholdChanged(_threshold: uint256) <<VaultStorage>> -    <<event>> SwapperChanged(_address: address) <<VaultStorage>> -    <<event>> SwapAllowedUndervalueChanged(_basis: uint256) <<VaultStorage>> -    <<event>> SwapSlippageChanged(_asset: address, _basis: uint256) <<VaultStorage>> -    <<event>> Swapped(_fromAsset: address, _toAsset: address, _fromAssetAmount: uint256, _toAssetAmount: uint256) <<VaultStorage>> -    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> -    <<event>> DripperChanged(_dripper: address) <<VaultStorage>> -    <<event>> RebasePerSecondMaxChanged(rebaseRatePerSecond: uint256) <<VaultStorage>> -    <<event>> DripDurationChanged(dripDuration: uint256) <<VaultStorage>> -    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> -    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> -    constructor() <<Governable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> +   vaultBuffer: uint256 <<VaultStorage>> +   autoAllocateThreshold: uint256 <<VaultStorage>> +   rebaseThreshold: uint256 <<VaultStorage>> +   oUSD: OUSD <<VaultStorage>> +   strategistAddr: address <<VaultStorage>> +   maxSupplyDiff: uint256 <<VaultStorage>> +   trusteeAddress: address <<VaultStorage>> +   trusteeFeeBps: uint256 <<VaultStorage>> +   isMintWhitelistedStrategy: mapping(address=>bool) <<VaultStorage>> +   withdrawalQueueMetadata: WithdrawalQueueMetadata <<VaultStorage>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<VaultStorage>> +   withdrawalClaimDelay: uint256 <<VaultStorage>> +   lastRebase: uint64 <<VaultStorage>> +   dripDuration: uint64 <<VaultStorage>> +   rebasePerSecondMax: uint64 <<VaultStorage>> +   rebasePerSecondTarget: uint64 <<VaultStorage>> +   defaultStrategy: address <<VaultStorage>> +   asset: address <<VaultStorage>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _mint(_amount: uint256) <<VaultCore>> +    _claimWithdrawal(requestId: uint256): (amount: uint256) <<VaultCore>> +    _postRedeem(_amount: uint256) <<VaultCore>> +    _allocate() <<VaultCore>> +    _rebase(): uint256 <<whenNotRebasePaused>> <<VaultCore>> +    _nextYield(supply: uint256, vaultValue: uint256): (yield: uint256, targetRate: uint256) <<VaultCore>> +    _totalValue(): (value: uint256) <<VaultCore>> +    _totalValueInVault(): (value: uint256) <<VaultCore>> +    _checkBalance(_asset: address): (balance: uint256) <<VaultCore>> +    _addWithdrawalQueueLiquidity(): (addedClaimable: uint256) <<VaultCore>> +    _assetAvailable(): (assetAvailable: uint256) <<VaultCore>> +    _min(a: uint256, b: uint256): uint256 <<VaultCore>> +    _max(a: uint256, b: uint256): uint256 <<VaultCore>> +    _depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> +    _withdrawFromStrategy(_recipient: address, _strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<VaultAdmin>> +    _withdrawAllFromStrategy(_strategyAddr: address) <<VaultAdmin>> +    _withdrawAllFromStrategies() <<VaultAdmin>> +External: +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    initialize(_oToken: address) <<onlyGovernor, initializer>> <<VaultInitializer>> +    mint(address, _amount: uint256, uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    mint(_amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    mintForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>> +    burnForStrategy(_amount: uint256) <<whenNotCapitalPaused>> <<VaultCore>> +    requestWithdrawal(_amount: uint256): (requestId: uint256, queued: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    claimWithdrawal(_requestId: uint256): (amount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    claimWithdrawals(_requestIds: uint256[]): (amounts: uint256[], totalAmount: uint256) <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    allocate() <<whenNotCapitalPaused, nonReentrant>> <<VaultCore>> +    rebase() <<nonReentrant>> <<VaultCore>> +    previewYield(): (yield: uint256) <<VaultCore>> +    totalValue(): (value: uint256) <<VaultCore>> +    checkBalance(_asset: address): uint256 <<VaultCore>> +    addWithdrawalQueueLiquidity() <<VaultCore>> +    getAllAssets(): address[] <<VaultCore>> +    getStrategyCount(): uint256 <<VaultCore>> +    getAllStrategies(): address[] <<VaultCore>> +    isSupportedAsset(_asset: address): bool <<VaultCore>> +    setVaultBuffer(_vaultBuffer: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    setAutoAllocateThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setRebaseThreshold(_threshold: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setStrategistAddr(_address: address) <<onlyGovernor>> <<VaultAdmin>> +    setDefaultStrategy(_strategy: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    setWithdrawalClaimDelay(_delay: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setRebaseRateMax(yearlyApr: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    setDripDuration(_dripDuration: uint256) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    approveStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> +    removeStrategy(_addr: address) <<onlyGovernor>> <<VaultAdmin>> +    addStrategyToMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<VaultAdmin>> +    removeStrategyFromMintWhitelist(strategyAddr: address) <<onlyGovernor>> <<VaultAdmin>> +    depositToStrategy(_strategyToAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> +    withdrawFromStrategy(_strategyFromAddress: address, _assets: address[], _amounts: uint256[]) <<onlyGovernorOrStrategist, nonReentrant>> <<VaultAdmin>> +    setMaxSupplyDiff(_maxSupplyDiff: uint256) <<onlyGovernor>> <<VaultAdmin>> +    setTrusteeAddress(_address: address) <<onlyGovernor>> <<VaultAdmin>> +    setTrusteeFeeBps(_basis: uint256) <<onlyGovernor>> <<VaultAdmin>> +    pauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    unpauseRebase() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    pauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    unpauseCapital() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<VaultAdmin>> +    withdrawAllFromStrategy(_strategyAddr: address) <<onlyGovernorOrStrategist>> <<VaultAdmin>> +    withdrawAllFromStrategies() <<onlyGovernorOrStrategist>> <<VaultAdmin>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> AssetAllocated(_asset: address, _strategy: address, _amount: uint256) <<VaultStorage>> +    <<event>> StrategyApproved(_addr: address) <<VaultStorage>> +    <<event>> StrategyRemoved(_addr: address) <<VaultStorage>> +    <<event>> Mint(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> Redeem(_addr: address, _value: uint256) <<VaultStorage>> +    <<event>> CapitalPaused() <<VaultStorage>> +    <<event>> CapitalUnpaused() <<VaultStorage>> +    <<event>> DefaultStrategyUpdated(_strategy: address) <<VaultStorage>> +    <<event>> RebasePaused() <<VaultStorage>> +    <<event>> RebaseUnpaused() <<VaultStorage>> +    <<event>> VaultBufferUpdated(_vaultBuffer: uint256) <<VaultStorage>> +    <<event>> AllocateThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> RebaseThresholdUpdated(_threshold: uint256) <<VaultStorage>> +    <<event>> StrategistUpdated(_address: address) <<VaultStorage>> +    <<event>> MaxSupplyDiffChanged(maxSupplyDiff: uint256) <<VaultStorage>> +    <<event>> YieldDistribution(_to: address, _yield: uint256, _fee: uint256) <<VaultStorage>> +    <<event>> TrusteeFeeBpsChanged(_basis: uint256) <<VaultStorage>> +    <<event>> TrusteeAddressChanged(_address: address) <<VaultStorage>> +    <<event>> StrategyAddedToMintWhitelist(strategy: address) <<VaultStorage>> +    <<event>> StrategyRemovedFromMintWhitelist(strategy: address) <<VaultStorage>> +    <<event>> RebasePerSecondMaxChanged(rebaseRatePerSecond: uint256) <<VaultStorage>> +    <<event>> DripDurationChanged(dripDuration: uint256) <<VaultStorage>> +    <<event>> WithdrawalRequested(_withdrawer: address, _requestId: uint256, _amount: uint256, _queued: uint256) <<VaultStorage>> +    <<event>> WithdrawalClaimed(_withdrawer: address, _requestId: uint256, _amount: uint256) <<VaultStorage>> +    <<event>> WithdrawalClaimable(_claimable: uint256, _newClaimable: uint256) <<VaultStorage>> +    <<event>> WithdrawalClaimDelayUpdated(_newDelay: uint256) <<VaultStorage>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> whenNotRebasePaused() <<VaultCore>> +    <<modifier>> whenNotCapitalPaused() <<VaultCore>> +    <<modifier>> onlyGovernorOrStrategist() <<VaultAdmin>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    constructor(_usdc: address) <<OUSDVault>> +    getAssetCount(): uint256 <<VaultCore>> diff --git a/contracts/docs/VaultStorage.svg b/contracts/docs/VaultStorage.svg index 0e415f8939..876c965fa2 100644 --- a/contracts/docs/VaultStorage.svg +++ b/contracts/docs/VaultStorage.svg @@ -4,358 +4,316 @@ - - + + StorageDiagram - - + + -8 - -VaultCore <<Contract>> - -slot - -0 - -1-50 - -51 - -52 - -53 - -54 - -55 - -56 - -57 - -58 - -59 - -60 - -61 - -62 - -63 - -64 - -65 - -66 - -67 - -68 - -69 - -70 - -71 - -72 - -73 - -74 - -75-76 - -77 - -78 - -79 - -80-122 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.______gap (1600) - -mapping(address=>Asset): VaultStorage.assets (32) - -address[]: VaultStorage.allAssets (32) - -mapping(address=>Strategy): VaultStorage.strategies (32) - -address[]: VaultStorage.allStrategies (32) - -unallocated (10) - -bool: VaultStorage.capitalPaused (1) - -bool: VaultStorage.rebasePaused (1) - -address: VaultStorage.priceProvider (20) - -uint256: VaultStorage.redeemFeeBps (32) - -uint256: VaultStorage.vaultBuffer (32) - -uint256: VaultStorage.autoAllocateThreshold (32) - -uint256: VaultStorage.rebaseThreshold (32) - -unallocated (12) - -OUSD: VaultStorage.oUSD (20) - -unallocated (12) - -address: VaultStorage._deprecated_rebaseHooksAddr (20) - -unallocated (12) - -address: VaultStorage._deprecated_uniswapAddr (20) - -unallocated (12) - -address: VaultStorage.strategistAddr (20) - -mapping(address=>address): VaultStorage.assetDefaultStrategies (32) - -uint256: VaultStorage.maxSupplyDiff (32) - -unallocated (12) - -address: VaultStorage.trusteeAddress (20) - -uint256: VaultStorage.trusteeFeeBps (32) - -address[]: VaultStorage._deprecated_swapTokens (32) - -unallocated (12) - -address: VaultStorage.ousdMetaStrategy (20) - -int256: VaultStorage.netOusdMintedForStrategy (32) - -uint256: VaultStorage.netOusdMintForStrategyThreshold (32) - -SwapConfig: VaultStorage.swapConfig (32) - -mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) - -unallocated (12) - -address: VaultStorage.dripper (20) - -WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) - -mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) - -uint256: VaultStorage.withdrawalClaimDelay (32) - -uint64: VaultStorage.rebasePerSecondTarget (8) - -uint64: VaultStorage.rebasePerSecondMax (8) - -uint64: VaultStorage.dripDuration (8) - -uint64: VaultStorage.lastRebase (8) - -uint256[43]: VaultStorage.__gap (1376) +6 + +OUSDVault <<Contract>> + +slot + +0 + +1-50 + +51 + +52 + +53 + +54 + +55 + +56 + +57 + +58 + +59 + +60 + +61 + +62 + +63 + +64 + +65 + +66 + +67 + +68 + +69 + +70 + +71 + +72 + +73 + +74 + +75-76 + +77 + +78 + +79 + +80 + +81-122 + +123 + +type: <inherited contract>.variable (bytes) + +unallocated (30) + +bool: Initializable.initializing (1) + +bool: Initializable.initialized (1) + +uint256[50]: Initializable.______gap (1600) + +uint256: VaultStorage._deprecated_assets (32) + +address[]: VaultStorage._deprecated_allAssets (32) + +mapping(address=>Strategy): VaultStorage.strategies (32) + +address[]: VaultStorage.allStrategies (32) + +unallocated (10) + +bool: VaultStorage.capitalPaused (1) + +bool: VaultStorage.rebasePaused (1) + +address: VaultStorage._deprecated_priceProvider (20) + +uint256: VaultStorage._deprecated_redeemFeeBps (32) + +uint256: VaultStorage.vaultBuffer (32) + +uint256: VaultStorage.autoAllocateThreshold (32) + +uint256: VaultStorage.rebaseThreshold (32) + +unallocated (12) + +OUSD: VaultStorage.oUSD (20) + +unallocated (12) + +address: VaultStorage._deprecated_rebaseHooksAddr (20) + +unallocated (12) + +address: VaultStorage._deprecated_uniswapAddr (20) + +unallocated (12) + +address: VaultStorage.strategistAddr (20) + +uint256: VaultStorage._deprecated_assetDefaultStrategies (32) + +uint256: VaultStorage.maxSupplyDiff (32) + +unallocated (12) + +address: VaultStorage.trusteeAddress (20) + +uint256: VaultStorage.trusteeFeeBps (32) + +address[]: VaultStorage._deprecated_swapTokens (32) + +unallocated (12) + +address: VaultStorage._deprecated_ousdMetaStrategy (20) + +int256: VaultStorage._deprecated_netOusdMintedForStrategy (32) + +uint256: VaultStorage._deprecated_netOusdMintForStrategyThreshold (32) + +uint256: VaultStorage._deprecated_swapConfig (32) + +mapping(address=>bool): VaultStorage.isMintWhitelistedStrategy (32) + +unallocated (12) + +address: VaultStorage._deprecated_dripper (20) + +WithdrawalQueueMetadata: VaultStorage.withdrawalQueueMetadata (64) + +mapping(uint256=>WithdrawalRequest): VaultStorage.withdrawalRequests (32) + +uint256: VaultStorage.withdrawalClaimDelay (32) + +uint64: VaultStorage.rebasePerSecondTarget (8) + +uint64: VaultStorage.rebasePerSecondMax (8) + +uint64: VaultStorage.dripDuration (8) + +uint64: VaultStorage.lastRebase (8) + +unallocated (12) + +address: VaultStorage.defaultStrategy (20) + +uint256[42]: VaultStorage.__gap (1344) + +uint256: VaultStorage._deprecated_wethAssetIndex (32) 1 - -Asset <<Struct>> - -offset - -0 - -type: variable (bytes) - -unallocated (27) - -uint16: allowedOracleSlippageBps (2) - -uint8: decimals (1) - -UnitConversion: unitConversion (1) - -bool: isSupported (1) + +address[]: _deprecated_allAssets <<Array>> +0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -8:8->1 - - +6:6->1 + + 2 - -address[]: allAssets <<Array>> -0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +Strategy <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (31) + +bool: isSupported (1) + +uint256: _deprecated (32) - + -8:10->2 - - +6:9->2 + + 3 - -Strategy <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (31) - -bool: isSupported (1) - -uint256: _deprecated (32) + +address[]: allStrategies <<Array>> +0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) - + -8:13->3 - - +6:11->3 + + 4 - -address[]: allStrategies <<Array>> -0x4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b8 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +WithdrawalQueueMetadata <<Struct>> + +slot + +75 + +76 + +type: variable (bytes) + +uint128: claimable (16) + +uint128: queued (16) + +uint128: nextWithdrawalIndex (16) + +uint128: claimed (16) - + -8:15->4 - - +6:38->4 + + 5 - -SwapConfig <<Struct>> - -slot - -72 - -type: variable (bytes) - -unallocated (10) - -uint16: allowedUndervalueBps (2) - -address: swapper (20) + +WithdrawalRequest <<Struct>> + +offset + +0 + +1 + +type: variable (bytes) + +unallocated (6) + +uint40: timestamp (5) + +bool: claimed (1) + +address: withdrawer (20) + +uint128: queued (16) + +uint128: amount (16) - + -8:37->5 - - - - - -6 - -WithdrawalQueueMetadata <<Struct>> - -slot - -75 - -76 - -type: variable (bytes) - -uint128: claimable (16) - -uint128: queued (16) - -uint128: nextWithdrawalIndex (16) - -uint128: claimed (16) - - - -8:44->6 - - - - - -7 - -WithdrawalRequest <<Struct>> - -offset - -0 - -1 - -type: variable (bytes) - -unallocated (6) - -uint40: timestamp (5) - -bool: claimed (1) - -address: withdrawer (20) - -uint128: queued (16) - -uint128: amount (16) - - - -8:50->7 - - +6:44->5 + + diff --git a/contracts/docs/generate.sh b/contracts/docs/generate.sh index f32d8a7601..5af8c5867b 100644 --- a/contracts/docs/generate.sh +++ b/contracts/docs/generate.sh @@ -147,23 +147,19 @@ sol2uml storage .. -c WOSonic -o WOSonicStorage.svg --hideExpand ______gap # contracts/vault -sol2uml .. -v -hv -hf -he -hs -hl -hi -b OETHBaseVaultCore,OETHBaseVaultAdmin,OSonicVaultCore,OSonicVaultAdmin -o VaultHierarchy.svg +sol2uml .. -v -hv -hf -he -hs -hl -hi -i prettier-plugin-solidity -b OUSDVault,OETHVault,OETHBaseVault,OSonicVault -o VaultHierarchy.svg -sol2uml .. -s -d 0 -b VaultCore -o VaultCoreSquashed.svg -sol2uml .. -s -d 0 -b VaultAdmin -o VaultAdminSquashed.svg -sol2uml storage .. -c VaultCore -o VaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OUSDVault -o VaultSquashed.svg +sol2uml storage .. -i prettier-plugin-solidity -c OUSDVault -o VaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens -sol2uml .. -s -d 0 -b OETHVaultCore -o OETHVaultCoreSquashed.svg -sol2uml .. -s -d 0 -b OETHVaultAdmin -o OETHVaultAdminSquashed.svg -sol2uml storage .. -c OETHVaultCore -o OETHVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OETHVault -o OETHVaultSquashed.svg +# sol2uml storage .. -i prettier-plugin-solidity -c OETHVault -o OETHVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens -sol2uml .. -s -d 0 -b OETHBaseVaultCore -o OETHBaseVaultCoreSquashed.svg -sol2uml .. -s -d 0 -b OETHBaseVaultAdmin -o OETHBaseVaultAdminSquashed.svg -sol2uml storage .. -c OETHBaseVaultCore -o OETHBaseVaultStorage.svg --hideExpand __gap,______gap +# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OETHBaseVault -o OETHBaseVaultSquashed.svg +# sol2uml storage .. -i prettier-plugin-solidity -c OETHBaseVault -o OETHBaseVaultStorage.svg --hideExpand __gap,______gap -sol2uml .. -s -d 0 -b OSonicVaultCore -o OSonicVaultCoreSquashed.svg -sol2uml .. -s -d 0 -b OSonicVaultAdmin -o OSonicVaultAdminSquashed.svg -sol2uml storage .. -c OSonicVaultCore -o OSonicVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens +# sol2uml .. -s -d 0 -i prettier-plugin-solidity -b OSonicVault -o OSonicVaultSquashed.svg +# sol2uml storage .. -i prettier-plugin-solidity -c OSonicVault -o OSonicVaultStorage.svg --hideExpand __gap,______gap,_deprecated_swapTokens # contracts/poolBooster # call stack size exceeded diff --git a/contracts/docs/plantuml/baseContracts.png b/contracts/docs/plantuml/baseContracts.png index 79bdd99246..c83d6fa1bf 100644 Binary files a/contracts/docs/plantuml/baseContracts.png and b/contracts/docs/plantuml/baseContracts.png differ diff --git a/contracts/docs/plantuml/baseContracts.puml b/contracts/docs/plantuml/baseContracts.puml index 8a0c6a44d4..32d991c24b 100644 --- a/contracts/docs/plantuml/baseContracts.puml +++ b/contracts/docs/plantuml/baseContracts.puml @@ -91,10 +91,10 @@ zap ..> oethv woeth ..> oeth oeth <.> oethv ' oethv <.> drip -oethv ..> oracle +bridgeStrat .> oracle oracle ..> chain -oethv <...> bridgeStrat -oethv <...> aeroStrat +oethv <..> bridgeStrat +oethv <..> aeroStrat bridgeStrat ..> bridged aeroStrat ...> pm diff --git a/contracts/docs/plantuml/ousdContracts.png b/contracts/docs/plantuml/ousdContracts.png index e58e9cae3e..f4cf7a2584 100644 Binary files a/contracts/docs/plantuml/ousdContracts.png and b/contracts/docs/plantuml/ousdContracts.png differ diff --git a/contracts/docs/plantuml/ousdContracts.puml b/contracts/docs/plantuml/ousdContracts.puml index 59be157672..942d7475d5 100644 --- a/contracts/docs/plantuml/ousdContracts.puml +++ b/contracts/docs/plantuml/ousdContracts.puml @@ -32,22 +32,15 @@ object "Curve USDC AMO\nStrategy" as curveAmoStrat <><> #DeepSkyB rewards: CRV } -' Oracle -object "OracleRouter" as oracle <> #DeepSkyBlue { -pairs: - USDC/USD -} - checker ..> ousd checker ..> vault wousd ..> ousd ousd <.> vault -vault ..> oracle ' Strategies -vault <...> musd -vault <...> curveAmoStrat +vault <..> musd +vault <..> curveAmoStrat @enduml \ No newline at end of file diff --git a/contracts/node.sh b/contracts/node.sh index 52166b041a..84b8fc293c 100755 --- a/contracts/node.sh +++ b/contracts/node.sh @@ -91,8 +91,6 @@ main() done printf "\n" echo "🟢 Node initialized" - - FORK_NETWORK_NAME=$FORK_NETWORK_NAME FORK=true npx hardhat fund --amount 100000 --network localhost --accountsfromenv true & # wait for subprocesses to finish for job in `jobs -p` diff --git a/contracts/package.json b/contracts/package.json index 99dff7e25c..ffcbf28364 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -31,8 +31,8 @@ "lint:js": "eslint \"test/**/*.js\" \"tasks/**/*.js\" \"deploy/**/*.js\"", "lint:sol": "solhint \"contracts/**/*.sol\"", "prettier": "yarn run prettier:js && yarn run prettier:sol", - "prettier:check": "prettier -c \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"smoke/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", - "prettier:js": "prettier --write \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"smoke/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", + "prettier:check": "prettier -c \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", + "prettier:js": "prettier --write \"*.js\" \"deploy/**/*.js\" \"scripts/**/*.js\" \"scripts/**/*.js\" \"tasks/**/*.js\" \"test/**/*.js\" \"utils/**/*.js\"", "prettier:sol": "prettier --write --plugin=prettier-plugin-solidity \"contracts/**/*.sol\"", "test": "rm -rf deployments/hardhat && IS_TEST=true npx hardhat test", "test:base": "rm -rf deployments/hardhat && UNIT_TESTS_NETWORK=base IS_TEST=true npx hardhat test", diff --git a/contracts/scripts/governor/propose.js b/contracts/scripts/governor/propose.js index 6be3eb9c38..8bfdddbd50 100644 --- a/contracts/scripts/governor/propose.js +++ b/contracts/scripts/governor/propose.js @@ -348,46 +348,6 @@ async function proposeUpgradeOusdArgs() { return { args, description }; } -// Returns the argument to use for sending a proposal to upgrade VaultCore. -async function proposeUpgradeVaultCoreArgs(config) { - const vaultProxy = await ethers.getContract("VaultProxy"); - - const args = await proposeArgs([ - { - contract: vaultProxy, - signature: "upgradeTo(address)", - args: [config.address], - }, - ]); - const description = "Upgrade VaultCore"; - return { args, description }; -} - -async function proposeUpgradeVaultCoreAndAdminArgs() { - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultCoreProxy = await ethers.getContractAt( - "VaultCore", - cVaultProxy.address - ); - const cVaultCore = await ethers.getContract("VaultCore"); - const cVaultAdmin = await ethers.getContract("VaultAdmin"); - - const args = await proposeArgs([ - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [cVaultCore.address], - }, - { - contract: cVaultCoreProxy, - signature: "setAdminImpl(address)", - args: [cVaultAdmin.address], - }, - ]); - const description = "Vault Core and Admin upgrade"; - return { args, description }; -} - // Returns the arguments to use for sending a proposal call to upgrade to a new MicOracle. // See migration 11_new_mix_oracle for reference. async function proposeUpgradeOracleArgs() { @@ -656,95 +616,6 @@ async function proposeProp14Args() { return { args, description }; } -// Args to send a proposal to: -// - upgrade Vault Core and Admin -// - set the Aave reward token address to zero address -// - set the liquidation thresholds on strategies (except Aave since it does not have a reward token) -async function proposeProp17Args() { - const cVaultProxy = await ethers.getContract("VaultProxy"); - const cVaultCoreProxy = await ethers.getContractAt( - "VaultCore", - cVaultProxy.address - ); - const cVaultCore = await ethers.getContract("VaultCore"); - const cVaultAdmin = await ethers.getContract("VaultAdmin"); - - const cAaveStrategyProxy = await ethers.getContract("AaveStrategyProxy"); - const cAaveStrategy = await ethers.getContractAt( - "AaveStrategy", - cAaveStrategyProxy.address - ); - - const cCompoundStrategyProxy = await ethers.getContract( - "CompoundStrategyProxy" - ); - const cCompoundStrategy = await ethers.getContractAt( - "CompoundStrategy", - cCompoundStrategyProxy.address - ); - - const cCurveUSDCStrategyProxy = await ethers.getContract( - "CurveUSDCStrategyProxy" - ); - const cCurveUSDCStrategy = await ethers.getContractAt( - "ThreePoolStrategy", - cCurveUSDCStrategyProxy.address - ); - - const cCurveUSDTStrategyProxy = await ethers.getContract( - "CurveUSDTStrategyProxy" - ); - const cCurveUSDTStrategy = await ethers.getContractAt( - "ThreePoolStrategy", - cCurveUSDTStrategyProxy.address - ); - - const args = await proposeArgs([ - { - contract: cVaultProxy, - signature: "upgradeTo(address)", - args: [cVaultCore.address], - }, - { - contract: cVaultCoreProxy, - signature: "setAdminImpl(address)", - args: [cVaultAdmin.address], - }, - { - contract: cAaveStrategy, - signature: "setRewardTokenAddress(address)", - args: [addresses.zero], - }, - { - contract: cCompoundStrategy, - signature: "setPTokenAddress(address,address)", - args: [addresses.mainnet.USDC, addresses.mainnet.cUSDC], - }, - { - contract: cCompoundStrategy, - signature: "setPTokenAddress(address,address)", - args: [addresses.mainnet.USDT, addresses.mainnet.cUSDT], - }, - { - contract: cCompoundStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [utils.parseUnits("1", 18)], // 1 COMP with precision 18 - }, - { - contract: cCurveUSDCStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [utils.parseUnits("200", 18)], // 200 CRV with precision 18 - }, - { - contract: cCurveUSDTStrategy, - signature: "setRewardLiquidationThreshold(uint256)", - args: [utils.parseUnits("200", 18)], // 200 CRV with precision 18 - }, - ]); - const description = "Prop 16"; - return { args, description }; -} - async function proposeSetRewardLiquidationThresholdArgs() { const cCompoundStrategyProxy = await ethers.getContract( "CompoundStrategyProxy" @@ -985,12 +856,6 @@ async function main(config) { } else if (config.upgradeOusd) { console.log("upgradeOusd proposal"); argsMethod = proposeUpgradeOusdArgs; - } else if (config.upgradeVaultCore) { - console.log("upgradeVaultCore proposal"); - argsMethod = proposeUpgradeVaultCoreArgs; - } else if (config.upgradeVaultCoreAndAdmin) { - console.log("upgradeVaultCoreAndAdmin proposal"); - argsMethod = proposeUpgradeVaultCoreAndAdminArgs; } else if (config.upgradeOracle) { console.log("upgradeOracle proposal"); argsMethod = proposeUpgradeOracleArgs; @@ -1018,9 +883,6 @@ async function main(config) { } else if (config.prop14) { console.log("prop14 proposal"); argsMethod = proposeProp14Args; - } else if (config.prop17) { - console.log("prop17 proposal"); - argsMethod = proposeProp17Args; } else if (config.pauseCapital) { console.log("pauseCapital"); argsMethod = proposePauseCapitalArgs; diff --git a/contracts/smoke/mintRedeemTest.js b/contracts/smoke/mintRedeemTest.js deleted file mode 100644 index 65d3683080..0000000000 --- a/contracts/smoke/mintRedeemTest.js +++ /dev/null @@ -1,206 +0,0 @@ -const { fund, mint } = require("../tasks/account"); -const { - usdtUnits, - ousdUnits, - usdcUnits, - daiUnits, - ousdUnitsFormat, - isWithinTolerance, -} = require("../test/helpers"); -const addresses = require("../utils/addresses"); -const erc20Abi = require("../test/abi/erc20.json"); - -let utils, BigNumber, usdt, dai, usdc, ousd, vault, signer, signer2; - -async function fundAccount4(hre) { - await fund( - { - num: 1, - amount: "3000", - }, - hre - ); -} - -const getUsdtBalance = async () => { - return await usdt.connect(signer).balanceOf(signer.address); -}; - -const getDaiBalance = async () => { - return await dai.connect(signer).balanceOf(signer.address); -}; - -const getUsdcBalance = async () => { - return await usdc.connect(signer).balanceOf(signer.address); -}; - -const getOusdBalance = async (signer) => { - return await ousd.connect(signer).balanceOf(signer.address); -}; - -const assertExpectedOusd = (bigNumber, bigNumberExpected, tolerance = 0.03) => { - if (!isWithinTolerance(bigNumber, bigNumberExpected, 0.03)) { - throw new Error( - `Unexpected OUSD value. Expected ${ousdUnitsFormat( - bigNumberExpected - )} with the tolerance of ${tolerance}. Received: ${ousdUnitsFormat( - bigNumber - )}` - ); - } -}; - -const assertExpectedStablecoins = ( - usdtBn, - daiBn, - usdcBn, - unitsExpected, - tolerance = 0.03 -) => { - // adjust decimals of all stablecoins to 18 so they are easier to compare - const adjustedUsdt = usdtBn.mul(BigNumber.from("1000000000000")); - const adjustedUsdc = usdcBn.mul(BigNumber.from("1000000000000")); - const allStablecoins = adjustedUsdt.add(adjustedUsdc).add(daiBn); - const stableCoinsExpected = utils.parseUnits(unitsExpected, 18); - - if (!isWithinTolerance(allStablecoins, stableCoinsExpected, 0.03)) { - throw new Error( - `Unexpected value. Expected to receive total stablecoin units ${ousdUnitsFormat( - stableCoinsExpected - )} with the tolerance of ${tolerance}. Received: ${ousdUnitsFormat( - allStablecoins - )}` - ); - } -}; - -async function setup(hre) { - utils = hre.ethers.utils; - BigNumber = hre.ethers.BigNumber; - ousd = await hre.ethers.getContractAt("OUSD", addresses.mainnet.OUSDProxy); - usdt = await hre.ethers.getContractAt(erc20Abi, addresses.mainnet.USDT); - dai = await hre.ethers.getContractAt(erc20Abi, addresses.mainnet.DAI); - usdc = await hre.ethers.getContractAt(erc20Abi, addresses.mainnet.USDC); - vault = await ethers.getContractAt("IVault", addresses.mainnet.VaultProxy); - signer = (await hre.ethers.getSigners())[4]; - signer2 = (await hre.ethers.getSigners())[5]; - - await fundAccount4(hre); -} - -async function beforeDeploy(hre) { - // fund stablecoins to the 4th account in signers - await setup(hre); - - const usdtBeforeMint = await getUsdtBalance(); - const ousdBeforeMint = await getOusdBalance(signer); - const usdtToMint = "1100"; - await mint( - { - num: 1, - amount: usdtToMint, - }, - hre - ); - - const usdtAfterMint = await getUsdtBalance(); - const ousdAfterMint = await getOusdBalance(signer); - - const expectedUsdt = usdtBeforeMint.sub(usdtUnits(usdtToMint)); - if (!usdtAfterMint.eq(expectedUsdt)) { - throw new Error( - `Incorrect usdt value. Got ${usdtAfterMint.toString()} expected: ${expectedUsdt.toString()}` - ); - } - - const expectedOusd = ousdBeforeMint.add(ousdUnits(usdtToMint)); - assertExpectedOusd(ousdAfterMint, expectedOusd); - - return { - ousdBeforeMint, - ousdAfterMint, - }; -} - -const testMint = async (hre, beforeDeployData) => { - const ousdBeforeMint = await getOusdBalance(signer); - await mint( - { - num: 1, - amount: "500", - }, - hre - ); - - const ousdAfterMint = await getOusdBalance(signer); - - if (!beforeDeployData.ousdAfterMint.eq(ousdBeforeMint)) { - throw new Error( - `Deploy changed the amount of ousd in user's account from ${ousdUnitsFormat( - beforeDeployData.ousdAfterMint - )} to ${ousdUnitsFormat(ousdBeforeMint)}` - ); - } - - return ousdAfterMint; -}; - -const testRedeem = async (ousdAfterMint) => { - const usdtBeforeRedeem = await getUsdtBalance(); - const daiBeforeRedeem = await getDaiBalance(); - const usdcBeforeRedeem = await getUsdcBalance(); - - const unitsToRedeem = "800"; - const ousdToRedeem = ousdUnits(unitsToRedeem); - await vault.connect(signer).redeem(ousdToRedeem, ousdUnits("770")); - - const ousdAfterRedeem = await getOusdBalance(signer); - const usdtAfterRedeem = await getUsdtBalance(); - const daiAfterRedeem = await getDaiBalance(); - const usdcAfterRedeem = await getUsdcBalance(); - - const expectedOusd = ousdAfterMint.sub(ousdToRedeem); - assertExpectedOusd(ousdAfterRedeem, expectedOusd, 0.0); - - assertExpectedStablecoins( - usdtAfterRedeem.sub(usdtBeforeRedeem), - daiAfterRedeem.sub(daiBeforeRedeem), - usdcAfterRedeem.sub(usdcBeforeRedeem), - "800" - ); -}; - -const testTransfer = async () => { - const ousdSenderBeforeSend = await getOusdBalance(signer); - const ousdReceiverBeforeSend = await getOusdBalance(signer2); - const ousdToTransfer = "245.5"; - - await ousd - .connect(signer) - .transfer(signer2.address, ousdUnits(ousdToTransfer)); - - const ousdSenderAfterSend = await getOusdBalance(signer); - const ousdReceiverAfterSend = await getOusdBalance(signer2); - - assertExpectedOusd( - ousdSenderAfterSend, - ousdSenderBeforeSend.sub(ousdUnits(ousdToTransfer)), - 0.0 - ); - assertExpectedOusd( - ousdReceiverAfterSend, - ousdReceiverBeforeSend.add(ousdUnits(ousdToTransfer)), - 0.0 - ); -}; - -async function afterDeploy(hre, beforeDeployData) { - const ousdAfterMint = await testMint(hre, beforeDeployData); - await testRedeem(ousdAfterMint); - await testTransfer(); -} - -module.exports = { - beforeDeploy, - afterDeploy, -}; diff --git a/contracts/tasks/account.js b/contracts/tasks/account.js index 5d211cbb2e..9f146cc7db 100644 --- a/contracts/tasks/account.js +++ b/contracts/tasks/account.js @@ -1,19 +1,3 @@ -// USDT has its own ABI because of non standard returns -const usdtAbi = require("../test/abi/usdt.json").abi; -const usdsAbi = require("../test/abi/erc20.json"); -const tusdAbi = require("../test/abi/erc20.json"); -const usdcAbi = require("../test/abi/erc20.json"); -const { hardhatSetBalance, setERC20TokenBalance } = require("../test/_fund"); - -// By default we use 10 test accounts. -const defaultNumAccounts = 10; - -// The first 4 hardhat accounts are reserved for use as the deployer, governor, etc... -const defaultAccountIndex = 4; - -// By default, fund each test account with 10k worth of each stable coin. -const defaultFundAmount = 10000; - /** * Prints test accounts. */ @@ -40,160 +24,6 @@ async function accounts(taskArguments, hre, privateKeys) { } } -/** - * Funds test accounts on local or fork with USDS, USDT, USDC and TUSD. - */ -async function fund(taskArguments, hre) { - const addresses = require("../utils/addresses"); - const { isFork, isLocalhost } = require("../test/helpers"); - - if (!isFork && !isLocalhost) { - throw new Error("Task can only be used on local or fork"); - } - - if (hre.network.config.chainId !== 1) { - // Skip funding if it's not mainnet - return; - } - - if (!process.env.ACCOUNTS_TO_FUND) { - // No need to fund accounts if no accounts to fund - return; - } - - let usdt, usds, tusd, usdc; - if (isFork) { - usdt = await hre.ethers.getContractAt(usdtAbi, addresses.mainnet.USDT); - usds = await hre.ethers.getContractAt(usdsAbi, addresses.mainnet.USDS); - tusd = await hre.ethers.getContractAt(tusdAbi, addresses.mainnet.TUSD); - usdc = await hre.ethers.getContractAt(usdcAbi, addresses.mainnet.USDC); - } else { - usdt = await hre.ethers.getContract("MockUSDT"); - usds = await hre.ethers.getContract("MockUSDS"); - tusd = await hre.ethers.getContract("MockTUSD"); - usdc = await hre.ethers.getContract("MockUSDC"); - } - - const signers = await hre.ethers.getSigners(); - - let accountsToFund; - let signersToFund; - - if (taskArguments.accountsfromenv) { - if (!isFork) { - throw new Error("accountsfromenv param only works in fork mode"); - } - accountsToFund = process.env.ACCOUNTS_TO_FUND.split(","); - } else { - const numAccounts = Number(taskArguments.num) || defaultNumAccounts; - const accountIndex = Number(taskArguments.account) || defaultAccountIndex; - - signersToFund = signers.splice(accountIndex, numAccounts); - accountsToFund = signersToFund.map((signer) => signer.address); - } - - const fundAmount = taskArguments.amount || defaultFundAmount; - - console.log(`USDS: ${usds.address}`); - console.log(`USDC: ${usdc.address}`); - console.log(`USDT: ${usdt.address}`); - console.log(`TUSD: ${tusd.address}`); - - const contractDataList = [ - { - name: "eth", - token: null, - }, - { - name: "usds", - token: usds, - }, - { - name: "usdc", - token: usdc, - }, - { - name: "usdt", - token: usdt, - }, - ]; - - for (let i = 0; i < accountsToFund.length; i++) { - const currentAccount = accountsToFund[i]; - await Promise.all( - contractDataList.map(async (contractData) => { - const { token, name } = contractData; - const usedFundAmount = token !== null ? fundAmount : "1000000"; - - if (!token) { - await hardhatSetBalance(currentAccount, usedFundAmount); - } else { - await setERC20TokenBalance( - currentAccount, - token, - usedFundAmount, - hre - ); - } - - console.log( - `Funded ${currentAccount} with ${usedFundAmount} ${name.toUpperCase()}` - ); - }) - ); - } -} - -// Sends OUSD to a destination address. -async function transfer(taskArguments) { - const { - ousdUnits, - ousdUnitsFormat, - isFork, - isLocalHost, - } = require("../test/helpers"); - - if (!isFork && !isLocalHost) { - throw new Error("Task can only be used on local or fork"); - } - - const ousdProxy = await ethers.getContract("OUSDProxy"); - const ousd = await ethers.getContractAt("OUSD", ousdProxy.address); - - const index = Number(taskArguments.index); - const amount = taskArguments.amount; - const to = taskArguments.to; - - const signers = await hre.ethers.getSigners(); - const signer = signers[index]; - - // Print balances prior to the transfer - console.log("\nOUSD balances prior transfer"); - console.log( - `${signer.address}: ${ousdUnitsFormat( - await ousd.balanceOf(signer.address) - )} OUSD` - ); - console.log(`${to}: ${ousdUnitsFormat(await ousd.balanceOf(to))} OUSD`); - - // Send OUSD. - console.log( - `\nTransferring ${amount} OUSD from ${signer.address} to ${to}...` - ); - await ousd.connect(signer).transfer(to, ousdUnits(amount)); - - // Print balances after to the transfer - console.log("\nOUSD balances after transfer"); - console.log( - `${signer.address}: ${ousdUnitsFormat( - await ousd.balanceOf(signer.address) - )} OUSD` - ); - console.log(`${to}: ${ousdUnitsFormat(await ousd.balanceOf(to))} OUSD`); -} - module.exports = { accounts, - fund, - transfer, }; diff --git a/contracts/tasks/tasks.js b/contracts/tasks/tasks.js index 32c5606409..c2065c31fc 100644 --- a/contracts/tasks/tasks.js +++ b/contracts/tasks/tasks.js @@ -1,5 +1,4 @@ const { subtask, task, types } = require("hardhat/config"); -const { fund } = require("./account"); const { debug } = require("./debug"); const { env } = require("./env"); const { setActionVars, updateAction } = require("./defender"); @@ -50,14 +49,12 @@ const { depositToStrategy, mint, rebase, - redeem, requestWithdrawal, claimWithdrawal, snapVault, withdrawFromStrategy, withdrawAllFromStrategy, withdrawAllFromStrategies, - yieldTask, } = require("./vault"); const { checkDelta, getDelta, takeSnapshot } = require("./valueChecker"); const { @@ -148,17 +145,6 @@ const log = require("../utils/logger")("tasks"); // Environment tasks. task("env", "Check env vars are properly set for a Mainnet deployment", env); -// Account tasks. -task("fund", "Fund accounts on local or fork") - .addOptionalParam("num", "Number of accounts to fund") - .addOptionalParam("index", "Account start index") - .addOptionalParam("amount", "Stable coin amount to fund each account with") - .addOptionalParam( - "accountsfromenv", - "Fund accounts from the .env file instead of mnemonic" - ) - .setAction(fund); - // Debug tasks. task("debug", "Print info about contracts and their configs", debug); @@ -166,7 +152,7 @@ task("debug", "Print info about contracts and their configs", debug); subtask("allowance", "Get the token allowance an owner has given to a spender") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -192,7 +178,7 @@ task("allowance").setAction(async (_, __, runSuper) => { subtask("balance", "Get the token balance of an account or contract") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -214,7 +200,7 @@ task("balance").setAction(async (_, __, runSuper) => { subtask("approve", "Approve an account or contract to spend tokens") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -238,7 +224,7 @@ task("approve").setAction(async (_, __, runSuper) => { subtask("transfer", "Transfer tokens to an account or contract") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -252,7 +238,7 @@ task("transfer").setAction(async (_, __, runSuper) => { subtask("transferFrom", "Transfer tokens from an account or contract") .addParam( "symbol", - "Symbol of the token. eg OETH, WETH, USDT or OGV", + "Symbol of the token. eg OETH, WETH, USDC or OGV", undefined, types.string ) @@ -353,12 +339,10 @@ task("rebase").setAction(async (_, __, runSuper) => { return runSuper(); }); -task("yield", "Artificially generate yield on the OUSD Vault", yieldTask); - subtask("mint", "Mint OTokens from the Vault using collateral assets") .addOptionalParam( "asset", - "Symbol of the collateral asset to deposit. eg WETH, wS, USDT, DAI or USDC", + "Symbol of the collateral asset to deposit. eg WETH, wS or USDC", undefined, types.string ) @@ -386,25 +370,6 @@ task("mint").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("redeem", "Redeem OTokens for collateral assets from the Vault") - .addParam("amount", "Amount of OTokens to burn", undefined, types.float) - .addOptionalParam( - "symbol", - "Symbol of the OToken. eg OETH or OUSD", - "OETH", - types.string - ) - .addOptionalParam( - "min", - "Minimum amount of collateral to receive", - 0, - types.float - ) - .setAction(redeem); -task("redeem").setAction(async (_, __, runSuper) => { - return runSuper(); -}); - subtask( "depositToStrategy", "Deposits vault collateral assets to a vault strategy" diff --git a/contracts/tasks/vault.js b/contracts/tasks/vault.js index a8a3d6be4c..540cb0bd09 100644 --- a/contracts/tasks/vault.js +++ b/contracts/tasks/vault.js @@ -1,7 +1,6 @@ const { formatUnits, parseUnits } = require("ethers/lib/utils"); const { getBlock } = require("./block"); -const addresses = require("../utils/addresses"); const { resolveAsset } = require("../utils/resolvers"); const { getSigner } = require("../utils/signers"); const { logTxDetails } = require("../utils/txLogger"); @@ -198,57 +197,6 @@ async function rebase({ symbol }, hre) { await logTxDetails(tx, "rebase"); } -/** - * Artificially generate yield on the vault by sending it USDT. - */ -async function yieldTask(_, hre) { - const usdtAbi = require("../test/abi/usdt.json").abi; - const { - ousdUnitsFormat, - usdtUnits, - usdtUnitsFormat, - isFork, - isLocalhost, - } = require("../test/helpers"); - if (!isFork && !isLocalhost) { - throw new Error("Task can only be used on local or fork"); - } - - let richSigner, usdt; - if (isFork) { - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [addresses.mainnet.Binance], - }); - richSigner = await hre.ethers.provider.getSigner(addresses.mainnet.Binance); - usdt = await hre.ethers.getContractAt(usdtAbi, addresses.mainnet.USDT); - } else { - const signers = await hre.ethers.getSigners(); - richSigner = signers; - usdt = await hre.ethers.getContract("MockUSDT"); - } - - const { vault, oToken } = await getContracts(hre, "OUSD"); - - log("Sending yield to vault"); - let usdtBalance = await usdt.balanceOf(vault.address); - log("USDT vault balance", usdtUnitsFormat(usdtBalance)); - let vaultValue = await vault.totalValue(); - log("Vault value", ousdUnitsFormat(vaultValue)); - let supply = await oToken.totalSupply(); - log("OUSD supply", ousdUnitsFormat(supply)); - - // Transfer 100k USDT to the vault. - await usdt.connect(richSigner).transfer(vault.address, usdtUnits("100000")); - - usdtBalance = await usdt.balanceOf(vault.address); - log("USDT vault balance", usdtUnitsFormat(usdtBalance)); - vaultValue = await vault.totalValue(); - log("Vault value", ousdUnitsFormat(vaultValue)); - supply = await oToken.totalSupply(); - log("OUSD supply", ousdUnitsFormat(supply)); -} - /** * Call the Vault's admin pauseCapital method. */ @@ -312,19 +260,6 @@ async function mint({ amount, asset, symbol, min, approve }, hre) { await logTxDetails(tx, "mint"); } -async function redeem({ amount, min, symbol }, hre) { - const signer = await getSigner(); - - const { vault } = await getContracts(hre, symbol); - - const oTokenUnits = parseUnits(amount.toString()); - const minUnits = parseUnits(min.toString()); - - log(`About to redeem ${amount} ${symbol}`); - const tx = await vault.connect(signer).redeem(oTokenUnits, minUnits); - await logTxDetails(tx, "redeem"); -} - async function resolveStrategyAddress(strategy, hre) { let strategyAddr = strategy; if (!strategy.match(ethereumAddress)) { @@ -476,12 +411,10 @@ module.exports = { depositToStrategy, mint, rebase, - redeem, requestWithdrawal, claimWithdrawal, snapVault, withdrawFromStrategy, withdrawAllFromStrategy, withdrawAllFromStrategies, - yieldTask, }; diff --git a/contracts/test/_fixture-base.js b/contracts/test/_fixture-base.js index 028e10d850..edeb7bd20e 100644 --- a/contracts/test/_fixture-base.js +++ b/contracts/test/_fixture-base.js @@ -27,18 +27,7 @@ let snapshotId; const baseFixtureWithMockedVaultAdminConfig = async () => { const fixture = await defaultFixture(); - - const cOETHVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); - const cOETHVaultAdmin = await ethers.getContractAt( - "IVault", - cOETHVaultProxy.address - ); - await deployWithConfirmation("MockOETHVaultAdmin", [fixture.weth.address]); - - const mockVaultAdmin = await ethers.getContract("MockOETHVaultAdmin"); - await cOETHVaultAdmin - .connect(fixture.governor) - .setAdminImpl(mockVaultAdmin.address); + await deployWithConfirmation("MockOETHVault", [fixture.weth.address]); fixture.oethbVault = await ethers.getContractAt( "IMockVault", diff --git a/contracts/test/_fixture-plume.js b/contracts/test/_fixture-plume.js index 7b2ba1f552..91581ff216 100644 --- a/contracts/test/_fixture-plume.js +++ b/contracts/test/_fixture-plume.js @@ -21,18 +21,7 @@ let snapshotId; const baseFixtureWithMockedVaultAdminConfig = async () => { const fixture = await defaultFixture(); - - const cOETHVaultProxy = await ethers.getContract("OETHPlumeVaultProxy"); - const cOETHVaultAdmin = await ethers.getContractAt( - "IVault", - cOETHVaultProxy.address - ); - await deployWithConfirmation("MockOETHVaultAdmin", [fixture.weth.address]); - - const mockVaultAdmin = await ethers.getContract("MockOETHVaultAdmin"); - await cOETHVaultAdmin - .connect(fixture.governor) - .setAdminImpl(mockVaultAdmin.address); + await deployWithConfirmation("MockOETHVault", [fixture.weth.address]); fixture.oethpVault = await ethers.getContractAt( "IMockVault", diff --git a/contracts/test/_fixture-sonic.js b/contracts/test/_fixture-sonic.js index 2dfef95c27..0d320dc748 100644 --- a/contracts/test/_fixture-sonic.js +++ b/contracts/test/_fixture-sonic.js @@ -237,8 +237,7 @@ async function swapXAMOFixture( ) { const fixture = await defaultSonicFixture(); - const { oSonic, oSonicVault, rafael, nick, strategist, timelock, wS } = - fixture; + const { oSonic, oSonicVault, rafael, nick, strategist, wS } = fixture; let swapXAMOStrategy, swapXPool, swapXGauge, swpx; @@ -264,10 +263,6 @@ async function swapXAMOFixture( swpx = await resolveAsset("SWPx"); } - await oSonicVault - .connect(timelock) - .setAssetDefaultStrategy(wS.address, addresses.zero); - // mint some OS using wS if configured if (config?.wsMintAmount > 0) { const wsAmount = parseUnits(config.wsMintAmount.toString()); @@ -279,7 +274,7 @@ async function swapXAMOFixture( const wsBalance = await wS.balanceOf(oSonicVault.address); const queue = await oSonicVault.withdrawalQueueMetadata(); const available = wsBalance.add(queue.claimed).sub(queue.queued); - const mintAmount = wsAmount.sub(available); + const mintAmount = wsAmount.sub(available).mul(10); if (mintAmount.gt(0)) { // Approve the Vault to transfer wS diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 8173f8e4f8..ef5a810b5b 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -20,7 +20,7 @@ const { deployWithConfirmation } = require("../utils/deploy"); const { replaceContractAt } = require("../utils/hardhat"); const { getAssetAddresses, - usdsUnits, + usdcUnits, getOracleAddresses, oethUnits, ousdUnits, @@ -216,7 +216,7 @@ const simpleOETHFixture = deployments.createFixture(async () => { }; }); -const getVaultAndTokenConracts = async () => { +const getVaultAndTokenContracts = async () => { const ousdProxy = await ethers.getContract("OUSDProxy"); const vaultProxy = await ethers.getContract("VaultProxy"); @@ -274,9 +274,9 @@ const createAccountTypes = async ({ vault, ousd, ousdUnlocked, deploy }) => { if (!isFork) { await fundAccounts(); - const usds = await ethers.getContract("MockUSDS"); - await usds.connect(matt).approve(vault.address, usdsUnits("1000")); - await vault.connect(matt).mint(usds.address, usdsUnits("1000"), 0); + const usdc = await ethers.getContract("MockUSDC"); + await usdc.connect(matt).approve(vault.address, usdcUnits("1000")); + await vault.connect(matt).mint(usdc.address, usdcUnits("1000"), 0); } const createAccount = async () => { @@ -450,10 +450,10 @@ const createAccountTypes = async ({ vault, ousd, ousdUnlocked, deploy }) => { rebase_delegate_target_1.address ); + // Allow matt to burn OUSD + await vault.connect(governor).setStrategistAddr(matt.address); // matt burn remaining OUSD - await vault - .connect(matt) - .redeem(ousd.balanceOf(matt.address), ousdUnits("0")); + await vault.connect(matt).requestWithdrawal(ousd.balanceOf(matt.address)); return { // StdRebasing account type: @@ -521,21 +521,23 @@ const loadTokenTransferFixture = deployments.createFixture(async () => { const { governorAddr, multichainStrategistAddr, timelockAddr } = await getNamedAccounts(); - const vaultAndTokenConracts = await getVaultAndTokenConracts(); + const vaultAndTokenContracts = await getVaultAndTokenContracts(); const signers = await hre.ethers.getSigners(); let governor = signers[1]; let strategist = signers[0]; + log("Creating account types..."); const accountTypes = await createAccountTypes({ - ousd: vaultAndTokenConracts.ousd, - ousdUnlocked: vaultAndTokenConracts.ousdUnlocked, - vault: vaultAndTokenConracts.vault, + ousd: vaultAndTokenContracts.ousd, + ousdUnlocked: vaultAndTokenContracts.ousdUnlocked, + vault: vaultAndTokenContracts.vault, deploy: deployments.deploy, }); + log("Account types created."); return { - ...vaultAndTokenConracts, + ...vaultAndTokenContracts, ...accountTypes, governorAddr, strategistAddr: multichainStrategistAddr, @@ -565,7 +567,7 @@ const defaultFixture = deployments.createFixture(async () => { const { governorAddr, multichainStrategistAddr, timelockAddr } = await getNamedAccounts(); - const vaultAndTokenConracts = await getVaultAndTokenConracts(); + const vaultAndTokenConracts = await getVaultAndTokenContracts(); const harvesterProxy = await ethers.getContract("HarvesterProxy"); const harvester = await ethers.getContractAt( @@ -1017,15 +1019,8 @@ const defaultFixture = deployments.createFixture(async () => { } if (!isFork) { - const assetAddresses = await getAssetAddresses(deployments); - const sGovernor = await ethers.provider.getSigner(governorAddr); - // Add TUSD in fixture, it is disabled by default in deployment - await vaultAndTokenConracts.vault - .connect(sGovernor) - .supportAsset(assetAddresses.TUSD, 0); - // Enable capital movement await vaultAndTokenConracts.vault.connect(sGovernor).unpauseCapital(); } @@ -1059,12 +1054,12 @@ const defaultFixture = deployments.createFixture(async () => { // Matt and Josh each have $100 OUSD & 100 OETH for (const user of [matt, josh]) { - await usds + await usdc .connect(user) - .approve(vaultAndTokenConracts.vault.address, usdsUnits("100")); + .approve(vaultAndTokenConracts.vault.address, usdcUnits("100")); await vaultAndTokenConracts.vault .connect(user) - .mint(usds.address, usdsUnits("100"), 0); + .mint(usdc.address, usdcUnits("100"), 0); // Fund WETH contract await hardhatSetBalance(user.address, "50000"); @@ -1180,6 +1175,7 @@ const defaultFixture = deployments.createFixture(async () => { oethMorphoAaveStrategy, convexEthMetaStrategy, oethDripper, + oethFixedRateDripperProxy, oethHarvester, oethZapper, swapper, @@ -1467,29 +1463,10 @@ async function compoundVaultFixture() { await fixture.compoundStrategy .connect(sGovernor) .setPTokenAddress(assetAddresses.USDT, assetAddresses.cUSDT); - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.compoundStrategy.address - ); // Add USDC await fixture.compoundStrategy .connect(sGovernor) .setPTokenAddress(assetAddresses.USDC, assetAddresses.cUSDC); - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.compoundStrategy.address - ); - // Add allocation mapping for USDS - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usds.address, - fixture.compoundStrategy.address - ); return fixture; } @@ -1513,16 +1490,7 @@ async function convexVaultFixture() { await fixture.vault .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.convexStrategy.address - ); - await fixture.vault - .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.convexStrategy.address - ); + .setDefaultStrategy(fixture.convexStrategy.address); return fixture; } @@ -1541,10 +1509,10 @@ async function balancerREthFixture(config = { defaultStrategy: true }) { if (config.defaultStrategy) { await oethVault .connect(timelock) - .setAssetDefaultStrategy(reth.address, balancerREthStrategy.address); + .setDefaultStrategy(reth.address, balancerREthStrategy.address); await oethVault .connect(timelock) - .setAssetDefaultStrategy(weth.address, balancerREthStrategy.address); + .setDefaultStrategy(weth.address, balancerREthStrategy.address); } fixture.rEthBPT = await ethers.getContractAt( @@ -1948,24 +1916,15 @@ async function morphoCompoundFixture() { if (isFork) { await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.morphoCompoundStrategy.address - ); + .setDefaultStrategy(fixture.morphoCompoundStrategy.address); await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.morphoCompoundStrategy.address - ); + .setDefaultStrategy(fixture.morphoCompoundStrategy.address); await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.dai.address, - fixture.morphoCompoundStrategy.address - ); + .setDefaultStrategy(fixture.morphoCompoundStrategy.address); } else { throw new Error( "Morpho strategy only supported in forked test environment" @@ -1986,10 +1945,7 @@ async function aaveFixture() { if (isFork) { await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.aaveStrategy.address - ); + .setDefaultStrategy(fixture.aaveStrategy.address); } else { throw new Error( "Aave strategy supported for USDT in forked test environment" @@ -2009,19 +1965,12 @@ async function morphoAaveFixture() { if (isFork) { // The supply of DAI and USDT has been paused for Morpho Aave V2 so no default strategy - await fixture.vault - .connect(timelock) - .setAssetDefaultStrategy(fixture.dai.address, addresses.zero); - await fixture.vault - .connect(timelock) - .setAssetDefaultStrategy(fixture.usdt.address, addresses.zero); + await fixture.vault.connect(timelock).setDefaultStrategy(addresses.zero); + await fixture.vault.connect(timelock).setDefaultStrategy(addresses.zero); await fixture.vault .connect(timelock) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.morphoAaveStrategy.address - ); + .setDefaultStrategy(fixture.morphoAaveStrategy.address); } else { throw new Error( "Morpho strategy only supported in forked test environment" @@ -2038,11 +1987,11 @@ async function oethMorphoAaveFixture() { const fixture = await oethDefaultFixture(); if (isFork) { - const { oethVault, timelock, weth, oethMorphoAaveStrategy } = fixture; + const { oethVault, timelock, oethMorphoAaveStrategy } = fixture; await oethVault .connect(timelock) - .setAssetDefaultStrategy(weth.address, oethMorphoAaveStrategy.address); + .setDefaultStrategy(oethMorphoAaveStrategy.address); } else { throw new Error( "Morpho strategy only supported in forked test environment" @@ -2084,7 +2033,7 @@ async function nativeStakingSSVStrategyFixture() { } else { fixture.ssvNetwork = await ethers.getContract("MockSSVNetwork"); const { governorAddr } = await getNamedAccounts(); - const { oethVault, weth, nativeStakingSSVStrategy } = fixture; + const { oethVault, nativeStakingSSVStrategy } = fixture; const sGovernor = await ethers.provider.getSigner(governorAddr); // Approve Strategy @@ -2098,7 +2047,7 @@ async function nativeStakingSSVStrategyFixture() { // Set as default await oethVault .connect(sGovernor) - .setAssetDefaultStrategy(weth.address, nativeStakingSSVStrategy.address); + .setDefaultStrategy(nativeStakingSSVStrategy.address); await nativeStakingSSVStrategy .connect(sGovernor) @@ -2171,7 +2120,7 @@ async function compoundingStakingSSVStrategyFixture() { } else { fixture.ssvNetwork = await ethers.getContract("MockSSVNetwork"); const { governorAddr, registratorAddr } = await getNamedAccounts(); - const { oethVault, weth } = fixture; + const { oethVault } = fixture; const sGovernor = await ethers.provider.getSigner(governorAddr); const sRegistrator = await ethers.provider.getSigner(registratorAddr); @@ -2183,10 +2132,7 @@ async function compoundingStakingSSVStrategyFixture() { // Set as default await oethVault .connect(sGovernor) - .setAssetDefaultStrategy( - weth.address, - compoundingStakingSSVStrategy.address - ); + .setDefaultStrategy(compoundingStakingSSVStrategy.address); await compoundingStakingSSVStrategy .connect(sGovernor) @@ -2294,17 +2240,11 @@ async function convexGeneralizedMetaForkedFixture( await fixture.vault .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdt.address, - fixture.metaStrategy.address - ); + .setDefaultStrategy(fixture.metaStrategy.address); await fixture.vault .connect(sGovernor) - .setAssetDefaultStrategy( - fixture.usdc.address, - fixture.metaStrategy.address - ); + .setDefaultStrategy(fixture.metaStrategy.address); return fixture; } @@ -2370,9 +2310,7 @@ async function convexOETHMetaVaultFixture( .connect(timelock) .setNetOusdMintForStrategyThreshold(parseUnits("500", 21)); - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(weth.address, addresses.zero); + await oethVault.connect(timelock).setDefaultStrategy(addresses.zero); // Impersonate the OETH Vault fixture.oethVaultSigner = await impersonateAndFund(oethVault.address); @@ -2589,15 +2527,27 @@ async function hackedVaultFixture() { /** * Instant rebase vault, for testing systems external to the vault */ -async function instantRebaseVaultFixture() { +async function instantRebaseVaultFixture(tokenName) { const fixture = await defaultFixture(); const { deploy } = deployments; const { governorAddr } = await getNamedAccounts(); const sGovernor = await ethers.provider.getSigner(governorAddr); + // Default to "usdc" if tokenName not provided + const name = tokenName ? tokenName.toLowerCase() : "usdc"; + let deployTokenAddress; + if (name === "usdc") { + deployTokenAddress = fixture.usdc.address; + } else if (name === "weth") { + deployTokenAddress = fixture.weth.address; + } else { + throw new Error(`Unsupported token name: ${name}`); + } + await deploy("MockVaultCoreInstantRebase", { from: governorAddr, + args: [deployTokenAddress], }); const instantRebase = await ethers.getContract("MockVaultCoreInstantRebase"); @@ -2623,7 +2573,7 @@ async function rebornFixture() { await deploy("Sanctum", { from: governorAddr, - args: [assetAddresses.USDS, vault.address], + args: [assetAddresses.USDC, vault.address], }); const sanctum = await ethers.getContract("Sanctum"); @@ -2660,7 +2610,7 @@ async function rebornFixture() { async function buybackFixture() { const fixture = await defaultFixture(); - const { ousd, oeth, oethVault, vault, weth, usds, josh, governor, timelock } = + const { ousd, oeth, oethVault, vault, weth, usdc, josh, governor, timelock } = fixture; const ousdBuybackProxy = await ethers.getContract("BuybackProxy"); @@ -2703,15 +2653,15 @@ async function buybackFixture() { // Load with funds to test swaps await setERC20TokenBalance(josh.address, weth, "10000"); - await setERC20TokenBalance(josh.address, usds, "10000"); + await setERC20TokenBalance(josh.address, usdc, "10000"); await weth.connect(josh).approve(oethVault.address, oethUnits("10000")); - await usds.connect(josh).approve(vault.address, ousdUnits("10000")); + await usdc.connect(josh).approve(vault.address, usdcUnits("10000")); // Mint & transfer oToken await oethVault.connect(josh).mint(weth.address, oethUnits("1.23"), "0"); await oeth.connect(josh).transfer(oethBuyback.address, oethUnits("1.1")); - await vault.connect(josh).mint(usds.address, oethUnits("1231"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("1231"), "0"); await ousd.connect(josh).transfer(ousdBuyback.address, oethUnits("1100")); await setERC20TokenBalance(armBuyback.address, weth, "100"); @@ -2724,9 +2674,9 @@ async function buybackFixture() { fixture.cvxLocker = await ethers.getContract("MockCVXLocker"); // Mint some OUSD - await usds.connect(josh).mint(ousdUnits("3000")); - await usds.connect(josh).approve(vault.address, ousdUnits("3000")); - await vault.connect(josh).mint(usds.address, ousdUnits("3000"), "0"); + await usdc.connect(josh).mint(usdcUnits("3000")); + await usdc.connect(josh).approve(vault.address, usdcUnits("3000")); + await vault.connect(josh).mint(usdc.address, usdcUnits("3000"), "0"); // Mint some OETH await weth.connect(josh).mint(oethUnits("3")); @@ -2759,7 +2709,6 @@ async function harvesterFixture() { vault, governor, harvester, - usdc, aaveStrategy, comp, aaveToken, @@ -2775,9 +2724,7 @@ async function harvesterFixture() { .setSupportedStrategy(aaveStrategy.address, true); // Add direct allocation of USDC to Aave - await vault - .connect(governor) - .setAssetDefaultStrategy(usdc.address, aaveStrategy.address); + await vault.connect(governor).setDefaultStrategy(aaveStrategy.address); // Let strategies hold some reward tokens await comp diff --git a/contracts/test/_hot-deploy.js b/contracts/test/_hot-deploy.js index 403b0bdd35..59e7c94151 100644 --- a/contracts/test/_hot-deploy.js +++ b/contracts/test/_hot-deploy.js @@ -12,7 +12,6 @@ const { oethPoolLpPID, } = require("../utils/constants"); const { replaceContractAt } = require("../utils/hardhat"); -const { impersonateAndFund } = require("../utils/signers"); const log = require("../utils/logger")("test:fixtures:hot-deploy"); @@ -125,14 +124,13 @@ async function hotDeployOption( const { isOethFixture } = config; const deployStrat = hotDeployOptions.includes("strategy"); - const deployVaultCore = hotDeployOptions.includes("vaultCore"); - const deployVaultAdmin = hotDeployOptions.includes("vaultAdmin"); + const deployVault = hotDeployOptions.includes("vault"); const deployHarvester = hotDeployOptions.includes("harvester"); const deployOracleRouter = hotDeployOptions.includes("oracleRouter"); const deployBuyback = hotDeployOptions.includes("buyback"); log(`Running fixture hot deployment w/ config; isOethFixture:${isOethFixture} strategy:${!!deployStrat} - vaultCore:${!!deployVaultCore} vaultAdmin:${!!deployVaultAdmin} harvester:${!!deployHarvester}`); + vault:${!!deployVault} harvester:${!!deployHarvester}`); if (deployStrat) { if (fixtureName === "balancerREthFixture") { @@ -162,13 +160,8 @@ async function hotDeployOption( } } - if (deployVaultCore || deployVaultAdmin) { - await hotDeployVaultAdmin( - fixture, - deployVaultAdmin, - deployVaultCore, - isOethFixture - ); + if (deployVault) { + await hotDeployVault(fixture, isOethFixture); } if (deployHarvester) { await hotDeployHarvester(fixture, isOethFixture); @@ -182,66 +175,33 @@ async function hotDeployOption( } } -async function hotDeployVaultAdmin( - fixture, - deployVaultAdmin, - deployVaultCore, - isOeth -) { +async function hotDeployVault(fixture, isOeth) { const { deploy } = deployments; const vaultProxyName = `${isOeth ? "OETH" : ""}VaultProxy`; - const vaultCoreName = `${isOeth ? "OETH" : ""}VaultCore`; - const vaultAdminName = `${isOeth ? "OETH" : ""}VaultAdmin`; - const vaultVariableName = `${isOeth ? "oethVault" : "vault"}`; + const vaultName = `${isOeth ? "OETH" : "OUSD"}Vault`; const cVaultProxy = await ethers.getContract(vaultProxyName); - if (deployVaultAdmin) { - log(`Deploying new ${vaultAdminName} implementation`); - - // deploy this contract that exposes internal function - await deploy(vaultAdminName, { - from: addresses.mainnet.Timelock, // doesn't matter which address deploys it - contract: vaultAdminName, - args: isOeth ? [fixture.weth.address] : [], - }); - - const implementation = await ethers.getContract(vaultAdminName); - const cVault = await ethers.getContractAt( - vaultCoreName, - cVaultProxy.address - ); - // TODO: this might be faster by replacing bytecode of existing implementation contract - const signerTimelock = await impersonateAndFund(addresses.mainnet.Timelock); - await cVault.connect(signerTimelock).setAdminImpl(implementation.address); - - fixture[vaultVariableName] = await ethers.getContractAt( - "IVault", - cVaultProxy.address - ); - } - if (deployVaultCore) { - log(`Deploying new ${vaultCoreName} implementation`); - // deploy this contract that exposes internal function - await deploy(vaultCoreName, { - from: addresses.mainnet.Timelock, // doesn't matter which address deploys it - contract: vaultCoreName, - args: isOeth ? [fixture.weth.address] : [], - }); - const implementation = await ethers.getContract(vaultCoreName); + log(`Deploying new ${vaultName} implementation`); + // deploy this contract that exposes internal function + await deploy(vaultName, { + from: addresses.mainnet.Timelock, // doesn't matter which address deploys it + contract: vaultName, + args: isOeth ? [fixture.weth.address] : [], + }); + const implementation = await ethers.getContract(vaultName); - const cVault = await ethers.getContractAt( - "InitializeGovernedUpgradeabilityProxy", - cVaultProxy.address - ); - const liveImplContractAddress = await cVault.implementation(); + const cVault = await ethers.getContractAt( + "InitializeGovernedUpgradeabilityProxy", + cVaultProxy.address + ); + const liveImplContractAddress = await cVault.implementation(); - log( - `Replacing implementation at ${liveImplContractAddress} with the fresh bytecode` - ); + log( + `Replacing implementation at ${liveImplContractAddress} with the fresh bytecode` + ); - await replaceContractAt(liveImplContractAddress, implementation); - } + await replaceContractAt(liveImplContractAddress, implementation); } async function hotDeployHarvester(fixture, forOETH) { diff --git a/contracts/test/_metastrategies-fixtures.js b/contracts/test/_metastrategies-fixtures.js index 119a0860c1..2f1bf7bb85 100644 --- a/contracts/test/_metastrategies-fixtures.js +++ b/contracts/test/_metastrategies-fixtures.js @@ -21,17 +21,10 @@ async function withDefaultOUSDMetapoolStrategiesSet() { const { vault, timelock, dai, usdt, usdc, OUSDmetaStrategy, daniel } = fixture; - await vault - .connect(timelock) - .setAssetDefaultStrategy(dai.address, OUSDmetaStrategy.address); + await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); - await vault - .connect(timelock) - .setAssetDefaultStrategy(usdt.address, OUSDmetaStrategy.address); - - await vault - .connect(timelock) - .setAssetDefaultStrategy(usdc.address, OUSDmetaStrategy.address); + await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); + await vault.connect(timelock).setDefaultStrategy(OUSDmetaStrategy.address); fixture.cvxRewardPool = await ethers.getContractAt( "IRewardStaking", diff --git a/contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js b/contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js deleted file mode 100644 index fe218604fb..0000000000 --- a/contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js +++ /dev/null @@ -1,35 +0,0 @@ -const { expect } = require("chai"); -const { createFixtureLoader, beaconChainFixture } = require("../_fixture"); - -const loadFixture = createFixtureLoader(beaconChainFixture); - -describe("ForkTest: Beacon Consolidation", function () { - this.timeout(0); - - let fixture; - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should get consolidation fee", async () => { - const { beaconConsolidation } = fixture; - - const fee = await beaconConsolidation.fee(); - expect(fee).to.be.gt(0); - expect(fee).to.be.lt(10); - }); - - it("Should request consolidation of validators", async () => { - const { beaconConsolidation, beaconConsolidationReplaced } = fixture; - - // These are two sweeping validators - const source = - "0xa31b5e5d655a06d849a36e5b03f1b9e647f911f38857c2a263973fba90f61b528173fb7a7cddd63dbe7e6604e7d61c87"; - const target = - "0xa258246e1217568a751670447879b7af5d6df585c59a15ebf0380f276069eadb11f30dea77cfb7357447dc24517be560"; - await beaconConsolidation.request(source, target); - - expect(await beaconConsolidationReplaced.lastSource()).to.equal(source); - expect(await beaconConsolidationReplaced.lastTarget()).to.equal(target); - }); -}); diff --git a/contracts/test/behaviour/harvester.js b/contracts/test/behaviour/harvester.js index dee9c505ac..12c080e32d 100644 --- a/contracts/test/behaviour/harvester.js +++ b/contracts/test/behaviour/harvester.js @@ -39,7 +39,7 @@ const { MAX_UINT256 } = require("../../utils/constants"); })); */ const shouldBehaveLikeHarvester = (context) => { - describe("Harvest behaviour", () => { + describe.skip("Harvest behaviour", () => { async function _checkBalancesPostHarvesting(harvestFn, strategies) { const { harvester } = context(); @@ -94,7 +94,7 @@ const shouldBehaveLikeHarvester = (context) => { }); }); - describe("RewardTokenConfig", () => { + describe.skip("RewardTokenConfig", () => { it("Should only allow valid Uniswap V2 path", async () => { const { harvester, crv, usdt, governor, uniswapRouter } = context(); @@ -409,8 +409,8 @@ const shouldBehaveLikeHarvester = (context) => { ).to.be.revertedWith("InvalidHarvestRewardBps"); }); - it("Should revert for unsupported tokens", async () => { - const { harvester, ousd, governor, uniswapRouter } = context(); + it.skip("Should revert for unsupported tokens", async () => { + const { harvester, governor, uniswapRouter, usdc } = context(); const config = { allowedSlippageBps: 133, @@ -424,7 +424,7 @@ const shouldBehaveLikeHarvester = (context) => { await expect( harvester .connect(governor) - .setRewardTokenConfig(ousd.address, config, []) + .setRewardTokenConfig(usdc.address, config, []) ).to.be.revertedWith("Asset not available"); }); @@ -448,7 +448,7 @@ const shouldBehaveLikeHarvester = (context) => { }); }); - describe("Swap", () => { + describe.skip("Swap", () => { async function _swapWithRouter(swapRouterConfig, swapData) { const { harvester, @@ -900,7 +900,7 @@ const shouldBehaveLikeHarvester = (context) => { }); }); - describe("Admin function", () => { + describe.skip("Admin function", () => { it("Should only allow governor to change RewardProceedsAddress", async () => { const { harvester, governor, daniel, strategist } = context(); diff --git a/contracts/test/behaviour/sfcStakingStrategy.js b/contracts/test/behaviour/sfcStakingStrategy.js index 4f2eaaa083..e2cd87e80d 100644 --- a/contracts/test/behaviour/sfcStakingStrategy.js +++ b/contracts/test/behaviour/sfcStakingStrategy.js @@ -31,13 +31,8 @@ const MIN_WITHDRAWAL_EPOCH_ADVANCE = 4; const shouldBehaveLikeASFCStakingStrategy = (context) => { describe("Initial setup", function () { it("Should verify the initial state", async () => { - const { - sonicStakingStrategy, - addresses, - oSonicVault, - testValidatorIds, - wS, - } = await context(); + const { sonicStakingStrategy, addresses, oSonicVault, testValidatorIds } = + await context(); expect(await sonicStakingStrategy.wrappedSonic()).to.equal( addresses.wS, "Incorrect wrapped sonic address set" @@ -77,16 +72,6 @@ const shouldBehaveLikeASFCStakingStrategy = (context) => { expect( (await sonicStakingStrategy.getRewardTokenAddresses()).length ).to.equal(0, "Incorrectly configured Reward Token Addresses"); - - expect(await oSonicVault.priceProvider()).to.not.equal( - AddressZero, - "Price provider address not set" - ); - - expect(await oSonicVault.priceUnitMint(wS.address)).to.equal( - oethUnits("1"), - "not expected PriceUnitMint" - ); }); }); diff --git a/contracts/test/behaviour/ssvStrategy.js b/contracts/test/behaviour/ssvStrategy.js index a7aa22b2ed..10a8e92dac 100644 --- a/contracts/test/behaviour/ssvStrategy.js +++ b/contracts/test/behaviour/ssvStrategy.js @@ -670,12 +670,12 @@ const shouldBehaveLikeAnSsvStrategy = (context) => { josh, nativeStakingSSVStrategy, nativeStakingFeeAccumulator, - oethFixedRateDripper, + oethFixedRateDripperProxy, weth, validatorRegistrator, } = await context(); const dripperWethBefore = await weth.balanceOf( - oethFixedRateDripper.address + oethFixedRateDripperProxy.address ); const strategyBalanceBefore = await nativeStakingSSVStrategy.checkBalance( weth.address @@ -712,15 +712,14 @@ const shouldBehaveLikeAnSsvStrategy = (context) => { nativeStakingSSVStrategy.address, weth.address, executionRewards.add(consensusRewards), - oethFixedRateDripper.address + oethFixedRateDripperProxy.address ); - // check balances after expect( await nativeStakingSSVStrategy.checkBalance(weth.address) ).to.equal(strategyBalanceBefore, "checkBalance should not increase"); - expect(await weth.balanceOf(oethFixedRateDripper.address)).to.equal( + expect(await weth.balanceOf(oethFixedRateDripperProxy.address)).to.equal( dripperWethBefore.add(executionRewards).add(consensusRewards), "Vault WETH balance should increase" ); diff --git a/contracts/test/governance/oethb-timelock.base.fork-test.js b/contracts/test/governance/oethb-timelock.base.fork-test.js index 279e23db98..d54e410a34 100644 --- a/contracts/test/governance/oethb-timelock.base.fork-test.js +++ b/contracts/test/governance/oethb-timelock.base.fork-test.js @@ -1,8 +1,8 @@ const { createFixtureLoader } = require("../_fixture"); const { defaultBaseFixture } = require("../_fixture-base"); const { expect } = require("chai"); -const addresses = require("../../utils/addresses"); const { advanceTime, advanceBlocks } = require("../helpers"); +const { parseUnits } = require("ethers/lib/utils"); const baseFixture = createFixtureLoader(defaultBaseFixture); @@ -15,9 +15,10 @@ describe("ForkTest: OETHb Timelock", function () { it("Multisig can propose and execute on Timelock", async () => { const { guardian, timelock, oethbVault } = fixture; + const newBufferValue = parseUnits("0.1", 18); const calldata = oethbVault.interface.encodeFunctionData( - "setDripper(address)", - [addresses.dead] + "setVaultBuffer(uint256)", + [newBufferValue] ); const args = [ @@ -41,6 +42,6 @@ describe("ForkTest: OETHb Timelock", function () { await timelock.connect(guardian).executeBatch(...args); - expect(await oethbVault.dripper()).to.eq(addresses.dead); + expect(await oethbVault.vaultBuffer()).to.eq(newBufferValue); }); }); diff --git a/contracts/test/hacks/reborn.js b/contracts/test/hacks/reborn.js index b7a420dee0..07875c93fd 100644 --- a/contracts/test/hacks/reborn.js +++ b/contracts/test/hacks/reborn.js @@ -1,7 +1,7 @@ const { expect } = require("chai"); const { createFixtureLoader, rebornFixture } = require("../_fixture"); -const { isFork, usdsUnits, ousdUnits } = require("../helpers"); +const { isFork, usdcUnits, ousdUnits } = require("../helpers"); describe("Reborn Attack Protection", function () { if (isFork) { @@ -15,9 +15,9 @@ describe("Reborn Attack Protection", function () { fixture = await loadFixture(); }); it("Should correctly do accounting when reborn calls mint as different types of addresses", async function () { - const { usds, ousd, matt, rebornAddress, reborner, deployAndCall } = + const { usdc, ousd, matt, rebornAddress, reborner, deployAndCall } = fixture; - await usds.connect(matt).transfer(rebornAddress, usdsUnits("4")); + await usdc.connect(matt).transfer(rebornAddress, usdcUnits("4")); // call mint and self destruct (since account.code.length = 0) in constructor this is done // as an EOA from OUSD.sol's point of view await deployAndCall({ shouldAttack: true, shouldDestruct: true }); @@ -31,10 +31,11 @@ describe("Reborn Attack Protection", function () { expect(await ousd.nonRebasingSupply()).to.equal(ousdUnits("2")); }); - it("Should correctly do accounting when reborn calls burn as different types of addresses", async function () { - const { usds, ousd, matt, reborner, rebornAddress, deployAndCall } = + // Skipped as instant redeem is no longer supported for ousd + it.skip("Should correctly do accounting when reborn calls burn as different types of addresses", async function () { + const { usdc, ousd, matt, reborner, rebornAddress, deployAndCall } = fixture; - await usds.connect(matt).transfer(reborner.address, usdsUnits("4")); + await usdc.connect(matt).transfer(reborner.address, usdcUnits("4")); // call mint and self destruct (since account.code.length = 0) in constructor this is done // as an EOA from OUSD.sol's point of view await deployAndCall({ shouldAttack: true, shouldDestruct: true }); @@ -50,9 +51,9 @@ describe("Reborn Attack Protection", function () { }); it("Should correctly do accounting when reborn calls transfer as different types of addresses", async function () { - const { usds, ousd, matt, reborner, rebornAddress, deployAndCall } = + const { usdc, ousd, matt, reborner, rebornAddress, deployAndCall } = fixture; - await usds.connect(matt).transfer(reborner.address, usdsUnits("4")); + await usdc.connect(matt).transfer(reborner.address, usdcUnits("4")); // call mint and self destruct (since account.code.length = 0) in constructor this is done // as an EOA from OUSD.sol's point of view await deployAndCall({ shouldAttack: true, shouldDestruct: true }); @@ -74,10 +75,10 @@ describe("Reborn Attack Protection", function () { }); it("Should have correct balance even after recreating", async function () { - const { usds, matt, reborner, deployAndCall, ousd } = fixture; + const { usdc, matt, reborner, deployAndCall, ousd } = fixture; // Mint one OUSD and self-destruct - await usds.connect(matt).transfer(reborner.address, usdsUnits("4")); + await usdc.connect(matt).transfer(reborner.address, usdcUnits("4")); await deployAndCall({ shouldAttack: true, shouldDestruct: true }); await expect(reborner).to.have.a.balanceOf("1", ousd); diff --git a/contracts/test/hacks/reentrant.js b/contracts/test/hacks/reentrant.js deleted file mode 100644 index 6892d52886..0000000000 --- a/contracts/test/hacks/reentrant.js +++ /dev/null @@ -1,22 +0,0 @@ -const { expect } = require("chai"); - -const { createFixtureLoader, hackedVaultFixture } = require("../_fixture"); -const { isFork } = require("../helpers"); - -describe("Reentry Attack Protection", function () { - if (isFork) { - this.timeout(0); - } - - describe("Vault", function () { - const loadFixture = createFixtureLoader(hackedVaultFixture); - it("Should not allow malicious coin to reentrant call vault function", async function () { - const { evilDAI, vault } = await loadFixture(); - - // to see this fail just comment out the require in the nonReentrant() in Governable.sol - await expect(vault.mint(evilDAI.address, 10, 0)).to.be.revertedWith( - "Reentrant call" - ); - }); - }); -}); diff --git a/contracts/test/oracle/oracle.js b/contracts/test/oracle/oracle.js deleted file mode 100644 index 943f5b3723..0000000000 --- a/contracts/test/oracle/oracle.js +++ /dev/null @@ -1,90 +0,0 @@ -const { expect } = require("chai"); - -const { loadDefaultFixture } = require("../_fixture"); -const { ousdUnits, setOracleTokenPriceUsd } = require("../helpers"); - -/* - * Because the oracle code is so tightly intergrated into the vault, - * the actual tests for the core oracle features are just a part of the vault tests. - */ - -describe("Oracle", async () => { - let fixture; - beforeEach(async () => { - fixture = await loadDefaultFixture(); - }); - describe("Oracle read methods for DAPP", () => { - it("should read the mint price", async () => { - const { vault, usdt } = fixture; - const tests = [ - ["0.998", "0.998"], - ["1.00", "1.00"], - ["1.05", "1.00"], - ]; - for (const test of tests) { - const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", actual); - expect(await vault.priceUnitMint(usdt.address)).to.equal( - ousdUnits(expectedRead) - ); - } - }); - - it("should fail below peg on the mint price", async () => { - const { vault, usdt } = fixture; - const prices = ["0.85", "0.997"]; - for (const price of prices) { - await setOracleTokenPriceUsd("USDT", price); - await expect(vault.priceUnitMint(usdt.address)).to.be.revertedWith( - "Asset price below peg" - ); - } - }); - - it("should read the redeem price", async () => { - const { vault, usdt } = fixture; - const tests = [ - ["0.80", "1.00"], - ["1.00", "1.00"], - ["1.05", "1.05"], - ]; - for (const test of tests) { - const [actual, expectedRead] = test; - await setOracleTokenPriceUsd("USDT", actual); - expect(await vault.priceUnitRedeem(usdt.address)).to.equal( - ousdUnits(expectedRead) - ); - } - }); - }); - - describe("Min/Max Drift", async () => { - const tests = [ - ["0.10", "Oracle: Price under min"], - ["0.699", "Oracle: Price under min"], - ["0.70"], - ["0.98"], - ["1.00"], - ["1.04"], - ["1.30"], - ["1.31", "Oracle: Price exceeds max"], - ["6.00", "Oracle: Price exceeds max"], - ]; - - for (const test of tests) { - const [price, expectedRevert] = test; - const revertLabel = expectedRevert ? "revert" : "not revert"; - const label = `Should ${revertLabel} because of drift at $${price}`; - it(label, async () => { - const { vault, usdt } = fixture; - await setOracleTokenPriceUsd("USDT", price); - if (expectedRevert) { - const tx = vault.priceUnitRedeem(usdt.address); - await expect(tx).to.be.revertedWith(expectedRevert); - } else { - await vault.priceUnitRedeem(usdt.address); - } - }); - } - }); -}); diff --git a/contracts/test/poolBooster/poolBooster.sonic.fork-test.js b/contracts/test/poolBooster/poolBooster.sonic.fork-test.js index 8257d9991f..c10119e858 100644 --- a/contracts/test/poolBooster/poolBooster.sonic.fork-test.js +++ b/contracts/test/poolBooster/poolBooster.sonic.fork-test.js @@ -646,7 +646,7 @@ describe("ForkTest: Pool Booster", function () { ], "PoolBoosterFactorySwapxSingle" ) - ).to.be.revertedWith("Invalid oSonic address"); + ).to.be.revertedWith("Invalid oToken address"); }); it("Can not deploy a factory with zero governor address", async () => { diff --git a/contracts/test/safe-modules/bridge-helper.base.fork-test.js b/contracts/test/safe-modules/bridge-helper.base.fork-test.js index d46b46a609..dd13824c32 100644 --- a/contracts/test/safe-modules/bridge-helper.base.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.base.fork-test.js @@ -41,7 +41,7 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { .bridgeWETHToEthereum(oethUnits("1")); }); - it("Should deposit wOETH for OETHb and redeem it for WETH", async () => { + it.skip("Should deposit wOETH for OETHb and redeem it for WETH", async () => { const { nick, _mintWETH, @@ -56,9 +56,9 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { } = fixture; // Make sure Vault has some WETH - _mintWETH(nick, "1"); - await weth.connect(nick).approve(oethbVault.address, oethUnits("1")); - await oethbVault.connect(nick).mint(weth.address, oethUnits("1"), "0"); + _mintWETH(nick, "10000"); + await weth.connect(nick).approve(oethbVault.address, oethUnits("10000")); + await oethbVault.connect(nick).mint(weth.address, oethUnits("10000"), "0"); // Update oracle price await woethStrategy.updateWOETHOraclePrice(); @@ -111,7 +111,7 @@ describe("ForkTest: Bridge Helper Safe Module (Base)", function () { ); }); - it("Should mint OETHb with WETH and redeem it for wOETH", async () => { + it.skip("Should mint OETHb with WETH and redeem it for wOETH", async () => { const { _mintWETH, oethbVault, diff --git a/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js b/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js index 7c4fb77fe3..e66bc9a134 100644 --- a/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.mainnet.fork-test.js @@ -5,7 +5,7 @@ const { const { oethUnits } = require("../helpers"); const { expect } = require("chai"); const addresses = require("../../utils/addresses"); -const { impersonateAndFund } = require("../../utils/signers"); +//const { impersonateAndFund } = require("../../utils/signers"); const mainnetFixture = createFixtureLoader(bridgeHelperModuleFixture); @@ -178,27 +178,19 @@ describe("ForkTest: Bridge Helper Safe Module (Ethereum)", function () { ); }); + // Redeem has been deprecated for now, so skipping this test + /* it("Should unwrap WOETH and redeem it to WETH", async () => { - const { - woeth, - weth, - josh, - timelock, - safeSigner, - bridgeHelperModule, - oethVault, - } = fixture; + const { woeth, weth, josh, safeSigner, bridgeHelperModule, oethVault } = + fixture; await oethVault.connect(josh).rebase(); // Do a huge yield deposit to fund the Vault - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(weth.address, addresses.zero); - await impersonateAndFund(josh.address, "3000"); - await weth.connect(josh).deposit({ value: oethUnits("2500") }); - await weth.connect(josh).approve(oethVault.address, oethUnits("2500")); - await oethVault.connect(josh).mint(weth.address, oethUnits("2000"), "0"); + await impersonateAndFund(josh.address, "10000"); + await weth.connect(josh).deposit({ value: oethUnits("9500") }); + await weth.connect(josh).approve(oethVault.address, oethUnits("9500")); + await oethVault.connect(josh).mint(weth.address, oethUnits("9000"), "0"); const woethAmount = oethUnits("1"); @@ -220,4 +212,5 @@ describe("ForkTest: Bridge Helper Safe Module (Ethereum)", function () { ); expect(woethBalanceAfter).to.eq(woethBalanceBefore.sub(woethAmount)); }); + */ }); diff --git a/contracts/test/safe-modules/bridge-helper.plume.fork-test.js b/contracts/test/safe-modules/bridge-helper.plume.fork-test.js index 3e1c58122c..41f5c35fd0 100644 --- a/contracts/test/safe-modules/bridge-helper.plume.fork-test.js +++ b/contracts/test/safe-modules/bridge-helper.plume.fork-test.js @@ -230,7 +230,7 @@ describe("ForkTest: Bridge Helper Safe Module (Plume)", function () { ); }); - it("Should mint OETHp with WETH and redeem it for wOETH", async () => { + it.skip("Should mint OETHp with WETH and redeem it for wOETH", async () => { const { _mintWETH, oethpVault, diff --git a/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js b/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js index dcadeb0b2e..a78a97bcde 100644 --- a/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js +++ b/contracts/test/strategies/base/aerodrome-amo.base.fork-test.js @@ -260,6 +260,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { governor, strategist, rafael, + nick, aeroSwapRouter, aeroNftManager, harvester, @@ -275,6 +276,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { governor = fixture.governor; strategist = fixture.strategist; rafael = fixture.rafael; + nick = fixture.nick; aeroSwapRouter = fixture.aeroSwapRouter; aeroNftManager = fixture.aeroNftManager; oethbVaultSigner = await impersonateAndFund(oethbVault.address); @@ -1276,6 +1278,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { await mintAndDepositToStrategy({ amount: oethUnits("5"), returnTransaction: true, + depositALotBefore: false, }); const { value, direction } = await quoteAmountToSwapBeforeRebalance({ @@ -1403,7 +1406,7 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { const user = userOverride || rafael; amount = amount || oethUnits("5"); - const balance = weth.balanceOf(user.address); + const balance = await weth.balanceOf(user.address); if (balance < amount) { await setERC20TokenBalance(user.address, weth, amount + balance, hre); } @@ -1418,11 +1421,19 @@ describe("ForkTest: Aerodrome AMO Strategy (Base)", async function () { userOverride, amount, returnTransaction, + depositALotBefore = false, } = {}) => { const user = userOverride || rafael; amount = amount || oethUnits("5"); + // Deposit a lot of WETH into the vault + if (depositALotBefore) { + const _amount = oethUnits("5000"); + await setERC20TokenBalance(nick.address, weth, _amount, hre); + await weth.connect(nick).approve(oethbVault.address, _amount); + await oethbVault.connect(nick).mint(weth.address, _amount, _amount); + } - const balance = weth.balanceOf(user.address); + const balance = await weth.balanceOf(user.address); if (balance < amount) { await setERC20TokenBalance(user.address, weth, amount + balance, hre); } diff --git a/contracts/test/strategies/convex.js b/contracts/test/strategies/convex.js index 640d87f10d..9f1da88d7d 100644 --- a/contracts/test/strategies/convex.js +++ b/contracts/test/strategies/convex.js @@ -2,7 +2,7 @@ const { expect } = require("chai"); const { createFixtureLoader, convexVaultFixture } = require("../_fixture"); const { - usdsUnits, + usdcUnits, ousdUnits, units, expectApproxSupply, @@ -14,32 +14,29 @@ describe("Convex Strategy", function () { this.timeout(0); } - let anna, - ousd, + let ousd, vault, governor, + strategist, crv, threePoolToken, convexStrategy, cvxBooster, - usdt, - usdc, - usds; + usdc; const mint = async (amount, asset) => { - await asset.connect(anna).mint(await units(amount, asset)); + await asset.connect(strategist).mint(await units(amount, asset)); await asset - .connect(anna) + .connect(strategist) .approve(vault.address, await units(amount, asset)); return await vault - .connect(anna) + .connect(strategist) .mint(asset.address, await units(amount, asset), 0); }; const loadFixture = createFixtureLoader(convexVaultFixture); beforeEach(async function () { const fixture = await loadFixture(); - anna = fixture.anna; vault = fixture.vault; ousd = fixture.ousd; governor = fixture.governor; @@ -47,61 +44,39 @@ describe("Convex Strategy", function () { threePoolToken = fixture.threePoolToken; convexStrategy = fixture.convexStrategy; cvxBooster = fixture.cvxBooster; - usdt = fixture.usdt; usdc = fixture.usdc; - usds = fixture.usds; + strategist = fixture.strategist; }); describe("Mint", function () { - it("Should stake USDT in Curve gauge via 3pool", async function () { - await expectApproxSupply(ousd, ousdUnits("200")); - await mint("30000.00", usdt); - await expectApproxSupply(ousd, ousdUnits("30200")); - await expect(anna).to.have.a.balanceOf("30000", ousd); - await expect(cvxBooster).has.an.approxBalanceOf("30000", threePoolToken); - }); - it("Should stake USDC in Curve gauge via 3pool", async function () { await expectApproxSupply(ousd, ousdUnits("200")); await mint("50000.00", usdc); await expectApproxSupply(ousd, ousdUnits("50200")); - await expect(anna).to.have.a.balanceOf("50000", ousd); - await expect(cvxBooster).has.an.approxBalanceOf("50000", threePoolToken); - }); - - it("Should use a minimum LP token amount when depositing USDT into 3pool", async function () { - await expect(mint("29000", usdt)).to.be.revertedWith( - "Slippage ruined your day" - ); - }); - - it("Should use a minimum LP token amount when depositing USDC into 3pool", async function () { - await expect(mint("29000", usdc)).to.be.revertedWith( - "Slippage ruined your day" - ); + await expect(strategist).to.have.a.balanceOf("50000", ousd); + await expect(cvxBooster).has.an.approxBalanceOf("50200", threePoolToken); }); }); describe("Redeem", function () { it("Should be able to unstake from gauge and return USDT", async function () { await expectApproxSupply(ousd, ousdUnits("200")); - await mint("10000.00", usds); await mint("10000.00", usdc); - await mint("10000.00", usdt); - await vault.connect(anna).redeem(ousdUnits("20000"), 0); - await expectApproxSupply(ousd, ousdUnits("10200")); + await vault.connect(governor).set; + await vault.connect(strategist).requestWithdrawal(ousdUnits("10000")); + await expectApproxSupply(ousd, ousdUnits("200")); }); }); describe("Utilities", function () { it("Should allow transfer of arbitrary token by Governor", async () => { - await usds.connect(anna).approve(vault.address, usdsUnits("8.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("8.0"), 0); - // Anna sends her OUSD directly to Strategy + await usdc.connect(strategist).approve(vault.address, usdcUnits("8.0")); + await vault.connect(strategist).mint(usdc.address, usdcUnits("8.0"), 0); + // Strategist sends her OUSD directly to Strategy await ousd - .connect(anna) + .connect(strategist) .transfer(convexStrategy.address, ousdUnits("8.0")); - // Anna asks Governor for help + // Strategist asks Governor for help await convexStrategy .connect(governor) .transferToken(ousd.address, ousdUnits("8.0")); @@ -109,10 +84,10 @@ describe("Convex Strategy", function () { }); it("Should not allow transfer of arbitrary token by non-Governor", async () => { - // Naughty Anna + // Naughty Strategist await expect( convexStrategy - .connect(anna) + .connect(strategist) .transferToken(ousd.address, ousdUnits("8.0")) ).to.be.revertedWith("Caller is not the Governor"); }); diff --git a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js index bb332e9f5b..f713fb8453 100644 --- a/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js +++ b/contracts/test/strategies/curve-amo-ousd.mainnet.fork-test.js @@ -233,6 +233,7 @@ describe("Curve AMO OUSD strategy", function () { )} usdc to the pool` ); + await setERC20TokenBalance(nick.address, usdc, attackerusdcAmount, hre); await usdc.connect(nick).approve(curvePool.address, attackerusdcAmount); // Attacker adds a lot of usdc into the pool // prettier-ignore diff --git a/contracts/test/strategies/dripper.js b/contracts/test/strategies/dripper.js index 9584574918..9a6209c713 100644 --- a/contracts/test/strategies/dripper.js +++ b/contracts/test/strategies/dripper.js @@ -7,7 +7,7 @@ const { const { usdtUnits, advanceTime } = require("../helpers"); describe("Dripper", async () => { - let dripper, usdt, vault, ousd, governor, josh; + let dripper, usdt, vault, governor, josh; const loadFixture = createFixtureLoader(instantRebaseVaultFixture); beforeEach(async () => { @@ -15,7 +15,6 @@ describe("Dripper", async () => { dripper = fixture.dripper; usdt = fixture.usdt; vault = fixture.vault; - ousd = fixture.ousd; governor = fixture.governor; josh = fixture.josh; @@ -81,16 +80,6 @@ describe("Dripper", async () => { await expectApproxCollectOf(expected, dripper.collect); }); }); - describe("collectAndRebase()", async () => { - it("transfers funds to the vault and rebases", async () => { - const beforeRct = await ousd.rebasingCreditsPerToken(); - await dripper.connect(governor).setDripDuration("20000"); - await advanceTime(1000); - await expectApproxCollectOf("50", dripper.collectAndRebase); - const afterRct = await ousd.rebasingCreditsPerToken(); - expect(afterRct).to.be.lt(beforeRct); - }); - }); describe("Drip math", async () => { it("gives all funds if collect is after the duration end", async () => { await dripper.connect(governor).setDripDuration("20000"); diff --git a/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js b/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js index f6bb3b7760..0945b59fe2 100644 --- a/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js +++ b/contracts/test/strategies/sonic/swapx-amo.sonic.fork-test.js @@ -112,7 +112,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { describe("with wS in the vault", () => { const loadFixture = createFixtureLoader(swapXAMOFixture, { - wsMintAmount: 5000, + wsMintAmount: 5000000, depositToStrategy: false, balancePool: true, }); @@ -514,7 +514,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { swapXAMOStrategy, wS, } = fixture; - const withdrawAmount = parseUnits("2000"); + const withdrawAmount = parseUnits("200"); const dataBeforeWithdraw = await snapData(); logSnapData( @@ -893,7 +893,7 @@ describe("Sonic ForkTest: SwapX AMO Strategy", function () { describe("with an insolvent vault", () => { const loadFixture = createFixtureLoader(swapXAMOFixture, { - wsMintAmount: 50000000, + wsMintAmount: 5000000, depositToStrategy: false, }); beforeEach(async () => { diff --git a/contracts/test/strategies/vault-value-checker.js b/contracts/test/strategies/vault-value-checker.js index 52f6a50531..5b6ecbab18 100644 --- a/contracts/test/strategies/vault-value-checker.js +++ b/contracts/test/strategies/vault-value-checker.js @@ -2,16 +2,17 @@ const { expect } = require("chai"); const { loadDefaultFixture } = require("../_fixture"); const { impersonateAndFund } = require("../../utils/signers"); +const { usdcUnits, ousdUnits } = require("../helpers"); describe("Check vault value", () => { - let vault, ousd, matt, usds, checker, vaultSigner; + let vault, ousd, matt, usdc, checker, vaultSigner; beforeEach(async () => { const fixture = await loadDefaultFixture(); vault = fixture.vault; ousd = fixture.ousd; matt = fixture.matt; - usds = fixture.usds; + usdc = fixture.usdc; checker = await ethers.getContract("VaultValueChecker"); vaultSigner = await ethers.getSigner(vault.address); await impersonateAndFund(vaultSigner.address); @@ -26,10 +27,10 @@ describe("Check vault value", () => { // Alter value if (vaultChange > 0) { - await usds.mintTo(vault.address, vaultChange); + await usdc.mintTo(vault.address, vaultChange); } else if (vaultChange < 0) { // transfer amount out of the vault - await usds + await usdc .connect(vaultSigner) .transfer(matt.address, vaultChange * -1, { gasPrice: 0 }); } @@ -77,58 +78,58 @@ describe("Check vault value", () => { it( "should succeed if vault gain was inside the allowed band", testChange({ - vaultChange: 200, - expectedProfit: 0, - profitVariance: 100, - supplyChange: 200, - expectedVaultChange: 200, - vaultChangeVariance: 100, + vaultChange: usdcUnits("2"), // In USDC, 6 decimals + expectedProfit: ousdUnits("0"), + profitVariance: ousdUnits("100"), + supplyChange: ousdUnits("2"), // In OUSD, 18 decimals + expectedVaultChange: ousdUnits("2"), + vaultChangeVariance: ousdUnits("100"), }) ); it( "should revert if vault gain less than allowed", testChange({ - vaultChange: 50, - expectedProfit: 125, - profitVariance: 25, - supplyChange: 2, - expectedVaultChange: 1, - vaultChangeVariance: 1, + vaultChange: usdcUnits("50"), + expectedProfit: ousdUnits("125"), + profitVariance: ousdUnits("25"), + supplyChange: ousdUnits("2"), + expectedVaultChange: ousdUnits("1"), + vaultChangeVariance: ousdUnits("1"), expectedRevert: "Profit too low", }) ); it( "should revert if vault gain more than allowed", testChange({ - vaultChange: 550, - expectedProfit: 500, - profitVariance: 50, - supplyChange: 2, - expectedVaultChange: 1, - vaultChangeVariance: 1, + vaultChange: usdcUnits("550"), + expectedProfit: ousdUnits("500"), + profitVariance: ousdUnits("50"), + supplyChange: ousdUnits("2"), + expectedVaultChange: ousdUnits("1"), + vaultChangeVariance: ousdUnits("1"), expectedRevert: "Vault value change too high", }) ); it( "should succeed if vault loss was inside the allowed band", testChange({ - vaultChange: -200, - expectedProfit: -200, - profitVariance: 100, - supplyChange: 0, - expectedVaultChange: -200, - vaultChangeVariance: 0, + vaultChange: usdcUnits("200").mul(-1), + expectedProfit: ousdUnits("200").mul(-1), + profitVariance: ousdUnits("100"), + supplyChange: ousdUnits("0"), + expectedVaultChange: ousdUnits("200").mul(-1), + vaultChangeVariance: ousdUnits("0"), }) ); it( "should revert if vault loss under allowed band", testChange({ - vaultChange: -400, - expectedProfit: -400, - profitVariance: 40, - supplyChange: 0, - expectedVaultChange: 0, - vaultChangeVariance: 100, + vaultChange: usdcUnits("40").mul(-1), + expectedProfit: ousdUnits("40").mul(-1), + profitVariance: ousdUnits("4"), + supplyChange: ousdUnits("0"), + expectedVaultChange: ousdUnits("0"), + vaultChangeVariance: ousdUnits("10"), expectedRevert: "Vault value change too low", }) ); @@ -136,12 +137,12 @@ describe("Check vault value", () => { it( "should revert if vault loss over allowed band", testChange({ - vaultChange: 100, - expectedProfit: 100, - profitVariance: 100, - supplyChange: 0, - expectedVaultChange: 0, - vaultChangeVariance: 50, + vaultChange: usdcUnits("100"), + expectedProfit: ousdUnits("100"), + profitVariance: ousdUnits("100"), + supplyChange: ousdUnits("0"), + expectedVaultChange: ousdUnits("0"), + vaultChangeVariance: ousdUnits("50"), expectedRevert: "Vault value change too high", }) ); diff --git a/contracts/test/token/ousd.js b/contracts/test/token/ousd.js index 6edf446b55..8ac1708298 100644 --- a/contracts/test/token/ousd.js +++ b/contracts/test/token/ousd.js @@ -6,7 +6,7 @@ const { } = require("../_fixture"); const { utils, BigNumber } = require("ethers"); -const { usdsUnits, ousdUnits, usdcUnits, isFork } = require("../helpers"); +const { ousdUnits, usdcUnits, isFork } = require("../helpers"); const zeroAddress = "0x0000000000000000000000000000000000000000"; @@ -718,23 +718,23 @@ describe("Token", function () { }); it("Should mint correct amounts on non-rebasing account without previously set creditsPerToken", async () => { - let { ousd, usds, vault, josh, mockNonRebasing } = fixture; + let { ousd, usdc, vault, josh, mockNonRebasing } = fixture; // Give contract 100 USDS from Josh - await usds + await usdc .connect(josh) - .transfer(mockNonRebasing.address, usdsUnits("100")); + .transfer(mockNonRebasing.address, usdcUnits("100")); await expect(mockNonRebasing).has.a.balanceOf("0", ousd); const totalSupplyBefore = await ousd.totalSupply(); await mockNonRebasing.approveFor( - usds.address, + usdc.address, vault.address, - usdsUnits("100") + usdcUnits("100") ); const tx = await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); await expect(tx) .to.emit(ousd, "AccountRebasingDisabled") @@ -759,22 +759,22 @@ describe("Token", function () { }); it("Should mint correct amounts on non-rebasing account with previously set creditsPerToken", async () => { - let { ousd, usds, vault, matt, usdc, josh, mockNonRebasing } = fixture; + let { ousd, usdc, vault, matt, josh, mockNonRebasing } = fixture; // Give contract 100 USDS from Josh - await usds + await usdc .connect(josh) - .transfer(mockNonRebasing.address, usdsUnits("100")); + .transfer(mockNonRebasing.address, usdcUnits("100")); await expect(mockNonRebasing).has.a.balanceOf("0", ousd); const totalSupplyBefore = await ousd.totalSupply(); await mockNonRebasing.approveFor( - usds.address, + usdc.address, vault.address, - usdsUnits("100") + usdcUnits("100") ); await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); expect(await ousd.totalSupply()).to.equal( totalSupplyBefore.add(ousdUnits("50")) @@ -793,8 +793,8 @@ describe("Token", function () { // Mint again await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); expect(await ousd.totalSupply()).to.equal( // Note 200 additional from simulated yield @@ -817,22 +817,22 @@ describe("Token", function () { }); it("Should burn the correct amount for non-rebasing account", async () => { - let { ousd, usds, vault, matt, usdc, josh, mockNonRebasing } = fixture; + let { ousd, usdc, vault, matt, josh, mockNonRebasing } = fixture; // Give contract 100 USDS from Josh - await usds + await usdc .connect(josh) - .transfer(mockNonRebasing.address, usdsUnits("100")); + .transfer(mockNonRebasing.address, usdcUnits("100")); await expect(mockNonRebasing).has.a.balanceOf("0", ousd); const totalSupplyBefore = await ousd.totalSupply(); await mockNonRebasing.approveFor( - usds.address, + usdc.address, vault.address, - usdsUnits("100") + usdcUnits("100") ); await mockNonRebasing.mintOusd( vault.address, - usds.address, - usdsUnits("50") + usdc.address, + usdcUnits("50") ); await expect(await ousd.totalSupply()).to.equal( totalSupplyBefore.add(ousdUnits("50")) diff --git a/contracts/test/token/woeth.js b/contracts/test/token/woeth.js index aa33d78f86..b4447d8726 100644 --- a/contracts/test/token/woeth.js +++ b/contracts/test/token/woeth.js @@ -11,7 +11,7 @@ describe("WOETH", function () { if (isFork) { this.timeout(0); } - const loadFixture = createFixtureLoader(instantRebaseVaultFixture); + const loadFixture = createFixtureLoader(instantRebaseVaultFixture, "weth"); let oeth, weth, woeth, oethVault, usds, matt, josh, governor; beforeEach(async () => { diff --git a/contracts/test/token/wousd.js b/contracts/test/token/wousd.js index c2f7ba0fa1..fab72b4d40 100644 --- a/contracts/test/token/wousd.js +++ b/contracts/test/token/wousd.js @@ -4,13 +4,13 @@ const { createFixtureLoader, instantRebaseVaultFixture, } = require("../_fixture"); -const { ousdUnits, usdsUnits, isFork } = require("../helpers"); +const { ousdUnits, usdcUnits, isFork } = require("../helpers"); describe("WOUSD", function () { if (isFork) { this.timeout(0); } - let ousd, wousd, vault, usds, matt, josh, governor; + let ousd, wousd, vault, usdc, matt, josh, governor; const loadFixture = createFixtureLoader(instantRebaseVaultFixture); beforeEach(async () => { @@ -18,7 +18,7 @@ describe("WOUSD", function () { ousd = fixture.ousd; wousd = fixture.wousd; vault = fixture.vault; - usds = fixture.usds; + usdc = fixture.usdc; matt = fixture.matt; josh = fixture.josh; governor = fixture.governor; @@ -34,13 +34,15 @@ describe("WOUSD", function () { await increaseOUSDSupplyAndRebase(await ousd.totalSupply()); }); - const increaseOUSDSupplyAndRebase = async (usdsAmount) => { - await usds.connect(matt).transfer(vault.address, usdsAmount); + const increaseOUSDSupplyAndRebase = async (usdcAmount) => { + await usdc.mintTo(matt.address, usdcAmount.div(1e12)); + await usdc.connect(matt).transfer(vault.address, usdcAmount.div(1e12)); await vault.rebase(); }; describe("Funds in, Funds out", async () => { it("should deposit at the correct ratio", async () => { + console.log((await wousd.balanceOf(josh.address)).toString()); await wousd.connect(josh).deposit(ousdUnits("50"), josh.address); await expect(josh).to.have.a.balanceOf("75", wousd); await expect(josh).to.have.a.balanceOf("50", ousd); @@ -70,7 +72,7 @@ describe("WOUSD", function () { describe("Collects Rebase", async () => { it("should increase with an OUSD rebase", async () => { await expect(wousd).to.have.approxBalanceOf("100", ousd); - await usds.connect(josh).transfer(vault.address, usdsUnits("200")); + await usdc.connect(josh).transfer(vault.address, usdcUnits("200")); await vault.rebase(); await expect(wousd).to.have.approxBalanceOf("150", ousd); }); @@ -86,12 +88,12 @@ describe("WOUSD", function () { describe("Token recovery", async () => { it("should allow a governor to recover tokens", async () => { - await usds.connect(matt).transfer(wousd.address, usdsUnits("2")); - await expect(wousd).to.have.a.balanceOf("2", usds); - await expect(governor).to.have.a.balanceOf("1000", usds); - await wousd.connect(governor).transferToken(usds.address, usdsUnits("2")); - await expect(wousd).to.have.a.balanceOf("0", usds); - await expect(governor).to.have.a.balanceOf("1002", usds); + await usdc.connect(matt).transfer(wousd.address, usdcUnits("2")); + await expect(wousd).to.have.a.balanceOf("2", usdc); + await expect(governor).to.have.a.balanceOf("1000", usdc); + await wousd.connect(governor).transferToken(usdc.address, usdcUnits("2")); + await expect(wousd).to.have.a.balanceOf("0", usdc); + await expect(governor).to.have.a.balanceOf("1002", usdc); }); it("should not allow a governor to collect OUSD", async () => { await expect( diff --git a/contracts/test/vault/collateral-swaps.mainnet.fork-test.js b/contracts/test/vault/collateral-swaps.mainnet.fork-test.js deleted file mode 100644 index cd9a769814..0000000000 --- a/contracts/test/vault/collateral-swaps.mainnet.fork-test.js +++ /dev/null @@ -1,332 +0,0 @@ -const { expect } = require("chai"); -const { parseUnits, formatUnits } = require("ethers/lib/utils"); - -const { - createFixtureLoader, - defaultFixture, - ousdCollateralSwapFixture, -} = require("../_fixture"); -const { getIInchSwapData, recodeSwapData } = require("../../utils/1Inch"); -const { decimalsFor, isCI } = require("../helpers"); -const { resolveAsset } = require("../../utils/resolvers"); - -const log = require("../../utils/logger")("test:fork:swaps"); - -describe.skip("ForkTest: OUSD Vault", function () { - this.timeout(0); - - // Retry up to 3 times on CI - this.retries(isCI ? 3 : 0); - - let fixture; - - describe("post deployment", () => { - const loadFixture = createFixtureLoader(defaultFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("should have swapper set", async () => { - const { vault, swapper } = fixture; - - expect(await vault.swapper()).to.equal(swapper.address); - }); - it("assets should have allowed slippage", async () => { - const { vault, usds, usdc, usdt } = fixture; - - const assets = [usds, usdc, usdt]; - const expectedDecimals = [18, 6, 6]; - const expectedConversions = [0, 0, 0]; - const expectedSlippage = [25, 25, 25]; - - for (let i = 0; i < assets.length; i++) { - const config = await vault.getAssetConfig(assets[i].address); - - expect(config.decimals, `decimals ${i}`).to.equal(expectedDecimals[i]); - expect(config.isSupported, `isSupported ${i}`).to.be.true; - expect(config.unitConversion, `unitConversion ${i}`).to.be.equal( - expectedConversions[i] - ); - expect( - config.allowedOracleSlippageBps, - `allowedOracleSlippageBps ${i}` - ).to.equal(expectedSlippage[i]); - } - }); - }); - - describe("Collateral swaps (Happy paths)", async () => { - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - const tests = [ - { - from: "USDS", - to: "USDT", - fromAmount: 1000000, - minToAssetAmount: 990000, - }, - { - from: "USDS", - to: "USDC", - fromAmount: 1000000, - minToAssetAmount: 999900, - slippage: 0.1, // Max 1Inch slippage - }, - { - from: "USDT", - to: "USDS", - fromAmount: 1000000, - minToAssetAmount: 998000, - }, - { - from: "USDT", - to: "USDC", - fromAmount: 1000000, - minToAssetAmount: 998000, - }, - { - from: "USDC", - to: "USDS", - fromAmount: 1000000, - minToAssetAmount: 999900, - slippage: 0.05, // Max 1Inch slippage - }, - { - from: "USDC", - to: "USDT", - fromAmount: 1000000, - minToAssetAmount: "990000", - slippage: 0.02, - approxFromBalance: true, - }, - ]; - for (const test of tests) { - it(`should be able to swap ${test.fromAmount} ${test.from} for a min of ${ - test.minToAssetAmount - } ${test.to} using ${test.protocols || "all"} protocols`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.vault, - }, - fixture - ); - }); - } - }); - - describe("Collateral swaps (Unhappy paths)", async () => { - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - const tests = [ - { - error: "", - from: "USDS", - to: "USDC", - fromAmount: 100, - minToAssetAmount: 105, - }, - { - error: "From asset is not supported", - from: "WETH", - to: "USDT", - fromAmount: 20, - minToAssetAmount: 1, - }, - { - error: "To asset is not supported", - from: "USDS", - to: "WETH", - fromAmount: 20, - minToAssetAmount: 1, - }, - { - error: "Usds/insufficient-balance", - from: "USDS", - to: "USDC", - fromAmount: 30000000, - minToAssetAmount: 29000000, - }, - { - error: "SafeERC20: low-level call failed", - from: "USDT", - to: "USDC", - fromAmount: 50000000, - minToAssetAmount: 49900000, - }, - { - error: "ERC20: transfer amount exceeds balance", - from: "USDC", - to: "USDS", - fromAmount: 30000000, - minToAssetAmount: 29900000, - }, - ]; - - for (const test of tests) { - it(`should fail to swap ${test.fromAmount} ${test.from} for ${ - test.to - } using ${test.protocols || "all"} protocols: error ${ - test.error - }`, async () => { - const fromAsset = await resolveAsset(test.from); - const toAsset = await resolveAsset(test.to); - await assertFailedSwap( - { - ...test, - fromAsset, - toAsset, - vault: fixture.vault, - }, - fixture - ); - }); - } - }); -}); -const assertSwap = async ( - { - fromAsset, - toAsset, - fromAmount, - minToAssetAmount, - slippage, - protocols, - approxFromBalance, - vault, - }, - fixture -) => { - const { strategist, swapper } = fixture; - - const fromAssetDecimals = await decimalsFor(fromAsset); - fromAmount = await parseUnits(fromAmount.toString(), fromAssetDecimals); - const toAssetDecimals = await decimalsFor(toAsset); - minToAssetAmount = await parseUnits( - minToAssetAmount.toString(), - toAssetDecimals - ); - - const apiEncodedData = await getIInchSwapData({ - vault: vault, - fromAsset, - toAsset, - fromAmount, - slippage, - protocols, - }); - - // re-encode the 1Inch tx.data from their swap API to the executer data - const swapData = await recodeSwapData(apiEncodedData); - - const fromBalanceBefore = await fromAsset.balanceOf(vault.address); - log( - `from asset balance before ${formatUnits( - fromBalanceBefore, - fromAssetDecimals - )}` - ); - const toBalanceBefore = await toAsset.balanceOf(vault.address); - - const tx = vault - .connect(strategist) - .swapCollateral( - fromAsset.address, - toAsset.address, - fromAmount, - minToAssetAmount, - swapData - ); - - // Asset events - await expect(tx).to.emit(vault, "Swapped").withNamedArgs({ - _fromAsset: fromAsset.address, - _toAsset: toAsset.address, - _fromAssetAmount: fromAmount, - }); - await expect(tx) - .to.emit(fromAsset, "Transfer") - .withArgs(vault.address, swapper.address, fromAmount); - - // Asset balances - const fromBalanceAfter = await fromAsset.balanceOf(vault.address); - if (approxFromBalance) { - expect( - fromBalanceBefore.sub(fromBalanceAfter), - "from asset approx bal" - ).to.approxEqualTolerance(fromAmount, 0.01); - } else { - expect(fromBalanceBefore.sub(fromBalanceAfter), "from asset bal").to.eq( - fromAmount - ); - } - const toBalanceAfter = await toAsset.balanceOf(vault.address); - log( - `to assets purchased ${formatUnits( - toBalanceAfter.sub(toBalanceBefore), - toAssetDecimals - )}` - ); - const toAmount = toBalanceAfter.sub(toBalanceBefore); - expect(toAmount, "to asset bal").to.gt(minToAssetAmount); - log( - `swapped ${formatUnits(fromAmount, fromAssetDecimals)} for ${formatUnits( - toAmount, - toAssetDecimals - )}` - ); -}; -const assertFailedSwap = async ( - { - fromAsset, - toAsset, - fromAmount, - minToAssetAmount, - slippage, - protocols, - error, - vault, - }, - fixture -) => { - const { strategist } = fixture; - - const fromAssetDecimals = await decimalsFor(fromAsset); - fromAmount = await parseUnits(fromAmount.toString(), fromAssetDecimals); - const toAssetDecimals = await decimalsFor(toAsset); - minToAssetAmount = parseUnits(minToAssetAmount.toString(), toAssetDecimals); - - const apiEncodedData = await getIInchSwapData({ - vault, - fromAsset, - toAsset, - fromAmount, - slippage, - protocols, - }); - - // re-encode the 1Inch tx.data from their swap API to the executer data - const swapData = await recodeSwapData(apiEncodedData); - - const tx = vault - .connect(strategist) - .swapCollateral( - fromAsset.address, - toAsset.address, - fromAmount, - minToAssetAmount, - swapData - ); - - await expect(tx).to.be.revertedWith(error); -}; diff --git a/contracts/test/vault/compound.js b/contracts/test/vault/compound.js index ccebaca644..a632a14ff9 100644 --- a/contracts/test/vault/compound.js +++ b/contracts/test/vault/compound.js @@ -8,13 +8,13 @@ const { ousdUnits, usdsUnits, usdcUnits, - usdtUnits, - tusdUnits, setOracleTokenPriceUsd, isFork, expectApproxSupply, } = require("../helpers"); -const addresses = require("../../utils/addresses"); +const { + increase, +} = require("@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time"); describe("Vault with Compound strategy", function () { if (isFork) { @@ -40,12 +40,12 @@ describe("Vault with Compound strategy", function () { }); it("Governor can call setPTokenAddress", async () => { - const { usds, ousd, matt, compoundStrategy } = fixture; + const { usdc, ousd, matt, compoundStrategy } = fixture; await expect( compoundStrategy .connect(matt) - .setPTokenAddress(ousd.address, usds.address) + .setPTokenAddress(ousd.address, usdc.address) ).to.be.revertedWith("Caller is not the Governor"); }); @@ -58,181 +58,105 @@ describe("Vault with Compound strategy", function () { }); it("Should allocate unallocated assets", async () => { - const { anna, governor, usds, usdc, usdt, tusd, vault, compoundStrategy } = - fixture; - - await usds.connect(anna).transfer(vault.address, usdsUnits("100")); - await usdc.connect(anna).transfer(vault.address, usdcUnits("200")); - await usdt.connect(anna).transfer(vault.address, usdtUnits("300")); - - await tusd.connect(anna).mint(ousdUnits("1000.0")); - await tusd.connect(anna).transfer(vault.address, tusdUnits("400")); + const { anna, governor, usdc, vault, compoundStrategy } = fixture; + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(anna).transfer(vault.address, usdcUnits("100")); await expect(vault.connect(governor).allocate()) .to.emit(vault, "AssetAllocated") - .withArgs(usds.address, compoundStrategy.address, usdsUnits("300")) - .to.emit(vault, "AssetAllocated") - .withArgs(usdc.address, compoundStrategy.address, usdcUnits("200")) - .to.emit(vault, "AssetAllocated") - .withArgs(usdt.address, compoundStrategy.address, usdcUnits("300")); - /* - TODO: There does not appear to be any support for .withoutArgs to verify - that this event doesn't get emitted. - .to.emit(vault, "AssetAllocated") - .withoutArgs(usdt.address, compoundStrategy.address, tusdUnits("400")); - */ + .withArgs(usdc.address, compoundStrategy.address, usdcUnits("300")); - // Note compoundVaultFixture sets up with 200 USDS already in the Strategy + // Note compoundVaultFixture sets up with 200 USDC already in the Strategy // 200 + 100 = 300 - await expect( - await compoundStrategy.checkBalance(usds.address) - ).to.approxEqual(usdsUnits("300")); await expect( await compoundStrategy.checkBalance(usdc.address) - ).to.approxEqual(usdcUnits("200")); - await expect( - await compoundStrategy.checkBalance(usdt.address) - ).to.approxEqual(usdtUnits("300")); - - // Strategy doesn't support TUSD - // Vault balance for TUSD should remain unchanged - expect(await tusd.balanceOf(vault.address)).to.equal(tusdUnits("400")); + ).to.approxEqual(usdcUnits("300")); }); it("Should correctly handle a deposit of USDC (6 decimals)", async function () { const { anna, ousd, usdc, vault } = fixture; await expect(anna).has.a.balanceOf("0", ousd); - // The mint process maxes out at a 1.0 price - await setOracleTokenPriceUsd("USDC", "1.25"); await usdc.connect(anna).approve(vault.address, usdcUnits("50")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); + await vault + .connect(anna) + .mint(usdc.address, usdcUnits("50"), ousdUnits("50")); await expect(anna).has.a.balanceOf("50", ousd); }); it("Should allow withdrawals", async () => { - const { anna, compoundStrategy, ousd, usdc, vault, governor } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - - await vault.connect(governor).allocate(); - - // Verify the deposit went to Compound - expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("50.0") - ); - - // Note Anna will have slightly less than 50 due to deposit to Compound - // according to the MockCToken implementation - await ousd.connect(anna).approve(vault.address, ousdUnits("40.0")); - await vault.connect(anna).redeem(ousdUnits("40.0"), 0); + const { strategist, ousd, usdc, vault } = fixture; - await expect(anna).has.an.approxBalanceOf("10", ousd); - // Vault has 200 USDS and 50 USDC, 50/250 * 40 USDC will come back - await expect(anna).has.an.approxBalanceOf("958", usdc); - }); - - it("Should calculate the balance correctly with USDS in strategy", async () => { - const { usds, vault, josh, compoundStrategy, governor } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - // Josh deposits USDS, 18 decimals - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); - - await vault.connect(governor).allocate(); + await expect(strategist).has.a.balanceOf("1000.00", usdc); + await usdc.connect(strategist).approve(vault.address, usdcUnits("50.0")); + await vault.connect(strategist).mint(usdc.address, usdcUnits("50.0"), 0); + await expect(strategist).has.a.balanceOf("50.00", ousd); - // Josh had 1000 USDS but used 100 USDS to mint OUSD in the fixture - await expect(josh).has.an.approxBalanceOf("878.0", usds, "Josh has less"); + await ousd.connect(strategist).approve(vault.address, ousdUnits("40.0")); + await vault.connect(strategist).requestWithdrawal(ousdUnits("40.0")); + await increase(60 * 10); // Advance 10 minutes + await vault.connect(strategist).claimWithdrawal(0); // Assumes request ID is 0 - // Verify the deposit went to Compound (as well as existing Vault assets) - expect(await compoundStrategy.checkBalance(usds.address)).to.approxEqual( - usdsUnits("222") - ); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("222", 18) - ); + await expect(strategist).has.an.balanceOf("10", ousd); + await expect(strategist).has.an.balanceOf("990.0", usdc); }); it("Should calculate the balance correctly with USDC in strategy", async () => { - const { usdc, vault, matt, compoundStrategy, governor } = fixture; + const { usdc, vault, josh, compoundStrategy, governor } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) ); - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + // Josh deposits USDC, 6 decimals + await usdc.connect(josh).approve(vault.address, usdcUnits("22.0")); + await vault.connect(josh).mint(usdc.address, usdcUnits("22.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); - // Verify the deposit went to Compound - await expect(matt).has.an.approxBalanceOf("992.0", usdc, "Matt has less"); + // Josh had 1000 USDC but used 100 USDC to mint OUSD in the fixture + await expect(josh).has.an.approxBalanceOf("878.0", usdc, "Josh has less"); + // Verify the deposit went to Compound (as well as existing Vault assets) expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8.0") + usdcUnits("222") ); expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("208", 18) + utils.parseUnits("222", 18) ); }); - it("Should calculate the balance correct with TUSD in Vault and USDS, USDC, USDT in Compound strategy", async () => { - const { - tusd, - usdc, - usds, - usdt, - vault, - matt, - josh, - anna, - governor, - compoundStrategy, - } = fixture; + it("Should calculate the balance correct with USDC in Vault and USDC in Compound strategy", async () => { + const { usdc, vault, matt, anna, governor, compoundStrategy } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) ); - // Josh deposits USDS, 18 decimals - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); // Existing 200 also ends up in strategy due to allocate call - expect(await compoundStrategy.checkBalance(usds.address)).to.approxEqual( - usdsUnits("222") + expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( + usdcUnits("200") ); // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); await vault.connect(governor).allocate(); expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8.0") + usdcUnits("208.0") ); - // Anna deposits USDT, 6 decimals - await usdt.connect(anna).approve(vault.address, usdtUnits("10.0")); - await vault.connect(anna).mint(usdt.address, usdtUnits("10.0"), 0); - await vault.connect(governor).allocate(); - expect(await compoundStrategy.checkBalance(usdt.address)).to.approxEqual( - usdtUnits("10.0") + // Anna deposits USDC that will stay in the Vault, 6 decimals + await usdc.connect(anna).approve(vault.address, usdcUnits("10.0")); + await vault.connect(anna).mint(usdc.address, usdcUnits("10.0"), 0); + expect(await usdc.balanceOf(vault.address)).to.approxEqual( + usdcUnits("10.0") ); - // Matt deposits TUSD, 18 decimals - await tusd.connect(matt).mint(ousdUnits("100.0")); - await tusd.connect(matt).approve(vault.address, tusdUnits("9.0")); - await vault.connect(matt).mint(tusd.address, tusdUnits("9.0"), 0); expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("249", 18) + utils.parseUnits("218", 18) ); }); @@ -265,8 +189,7 @@ describe("Vault with Compound strategy", function () { }); it("Should correctly withdrawAll all assets in Compound strategy", async () => { - const { usdc, vault, matt, josh, usds, compoundStrategy, governor } = - fixture; + const { usdc, vault, matt, compoundStrategy, governor } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) @@ -276,112 +199,61 @@ describe("Vault with Compound strategy", function () { await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8") + usdcUnits("208.0") ); - expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("208", 18) ); - - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); - - await vault.connect(governor).allocate(); - - expect(await compoundStrategy.checkBalance(usds.address)).to.approxEqual( - usdsUnits("222") - ); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) - ); - await compoundStrategy.connect(governor).withdrawAll(); // There should be no USDS or USDC left in compound strategy expect(await compoundStrategy.checkBalance(usdc.address)).to.equal(0); - expect(await compoundStrategy.checkBalance(usds.address)).to.equal(0); // Vault value should remain the same because the liquidattion sent the // assets back to the vault expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) + utils.parseUnits("208", 18) ); }); it("Should withdrawAll assets in Strategy and return them to Vault on removal", async () => { - const { - usdt, - usdc, - comp, - vault, - matt, - josh, - usds, - harvester, - compoundStrategy, - governor, - } = fixture; + const { usdc, vault, matt, compoundStrategy, governor } = fixture; expect(await vault.totalValue()).to.approxEqual( utils.parseUnits("200", 18) ); - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - await harvester.connect(governor).setRewardTokenConfig( - comp.address, // reward token - { - allowedSlippageBps: 300, - harvestRewardBps: 0, - swapPlatformAddr: mockUniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[comp.address, usdt.address]] - ) - ); // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).allocate(); expect(await compoundStrategy.checkBalance(usdc.address)).to.approxEqual( - usdcUnits("8.0") + usdcUnits("208.0") ); - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) + utils.parseUnits("208", 18) ); - await expect(await vault.getStrategyCount()).to.equal(1); + expect(await vault.getStrategyCount()).to.equal(1); await vault .connect(governor) - .setAssetDefaultStrategy(usdt.address, addresses.zero); - await vault - .connect(governor) - .setAssetDefaultStrategy(usdc.address, addresses.zero); - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, addresses.zero); + .setDefaultStrategy("0x0000000000000000000000000000000000000000"); await vault.connect(governor).removeStrategy(compoundStrategy.address); - await expect(await vault.getStrategyCount()).to.equal(0); - + expect(await vault.getStrategyCount()).to.equal(0); // Vault value should remain the same because the liquidattion sent the // assets back to the vault expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("230", 18) + utils.parseUnits("208", 18) ); // Should be able to add Strategy back. Proves the struct in the mapping @@ -390,24 +262,22 @@ describe("Vault with Compound strategy", function () { }); it("Should not alter balances after an asset price change", async () => { - let { ousd, vault, matt, usdc, usds } = fixture; + let { ousd, vault, matt, usdc } = fixture; await usdc.connect(matt).approve(vault.address, usdcUnits("200")); await vault.connect(matt).mint(usdc.address, usdcUnits("200"), 0); - await usds.connect(matt).approve(vault.address, usdsUnits("200")); - await vault.connect(matt).mint(usds.address, usdsUnits("200"), 0); // 200 OUSD was already minted in the fixture, 100 each for Matt and Josh - await expectApproxSupply(ousd, ousdUnits("600.0")); - // 100 + 200 + 200 - await expect(matt).has.an.approxBalanceOf("500", ousd, "Initial"); + await expectApproxSupply(ousd, ousdUnits("400.0")); + // 100 + 200 = 300 + await expect(matt).has.an.approxBalanceOf("300", ousd, "Initial"); await setOracleTokenPriceUsd("USDC", "1.30"); await vault.rebase(); - await expectApproxSupply(ousd, ousdUnits("600.0")); + await expectApproxSupply(ousd, ousdUnits("400.0")); await expect(matt).has.an.approxBalanceOf( - "500.00", + "300.00", ousd, "After some assets double" ); @@ -415,68 +285,14 @@ describe("Vault with Compound strategy", function () { await setOracleTokenPriceUsd("USDC", "1.00"); await vault.rebase(); - await expectApproxSupply(ousd, ousdUnits("600.0")); + await expectApproxSupply(ousd, ousdUnits("400.0")); await expect(matt).has.an.approxBalanceOf( - "500", + "300.00", ousd, "After assets go back" ); }); - it("Should handle non-standard token deposits", async () => { - let { ousd, vault, matt, nonStandardToken, oracleRouter, governor } = - fixture; - - await oracleRouter.cacheDecimals(nonStandardToken.address); - if (nonStandardToken) { - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - } - - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); - - await nonStandardToken - .connect(matt) - .approve(vault.address, usdtUnits("10000")); - - // Try to mint more than balance, to check failure state - try { - await vault - .connect(matt) - .mint(nonStandardToken.address, usdtUnits("1200"), 0); - } catch (err) { - expect( - /reverted with reason string 'SafeERC20: ERC20 operation did not succeed/gi.test( - err.message - ) - ).to.be.true; - } finally { - // Make sure nothing got affected - await expectApproxSupply(ousd, ousdUnits("200.0")); - await expect(matt).has.an.approxBalanceOf("100", ousd); - await expect(matt).has.an.approxBalanceOf("1000", nonStandardToken); - } - - // Try minting with a valid balance of tokens - await vault - .connect(matt) - .mint(nonStandardToken.address, usdtUnits("100"), 0); - await expect(matt).has.an.approxBalanceOf("900", nonStandardToken); - - await expectApproxSupply(ousd, ousdUnits("300.0")); - await expect(matt).has.an.approxBalanceOf("200", ousd, "Initial"); - await vault.rebase(); - await expect(matt).has.an.approxBalanceOf("200", ousd, "After null rebase"); - await setOracleTokenPriceUsd("NonStandardToken", "1.40"); - await vault.rebase(); - - await expectApproxSupply(ousd, ousdUnits("300.0")); - await expect(matt).has.an.approxBalanceOf( - "200.00", - ousd, - "After some assets double" - ); - }); - it("Should never allocate anything when Vault buffer is 1e18 (100%)", async () => { const { usds, vault, governor, compoundStrategy } = fixture; @@ -490,74 +306,26 @@ describe("Vault with Compound strategy", function () { await expect(await compoundStrategy.checkBalance(usds.address)).to.equal(0); }); - it("Should allocate correctly with USDS when Vault buffer is 1e17 (10%)", async () => { - const { usds, vault, governor, compoundStrategy } = await loadFixture( + it("Should allocate correctly with USDC when Vault buffer is 1e17 (10%)", async () => { + const { usdc, vault, governor, compoundStrategy } = await loadFixture( compoundVaultFixture ); - await expect(await vault.getStrategyCount()).to.equal(1); + expect(await vault.getStrategyCount()).to.equal(1); // Set a Vault buffer and allocate await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.allocate(); // Verify 80% went to Compound await expect( - await compoundStrategy.checkBalance(usds.address) - ).to.approxEqual(ousdUnits("180")); + await compoundStrategy.checkBalance(usdc.address) + ).to.approxEqual(usdcUnits("180")); // Remaining 20 should be in Vault await expect(await vault.totalValue()).to.approxEqual(ousdUnits("200")); }); - it("Should allocate correctly with USDS, USDT, USDC when Vault Buffer is 1e17 (10%)", async () => { - const { - usds, - usdc, - usdt, - matt, - josh, - vault, - anna, - governor, - compoundStrategy, - } = fixture; - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("200", 18) - ); - - // Josh deposits USDS, 18 decimals - await usds.connect(josh).approve(vault.address, usdsUnits("22.0")); - await vault.connect(josh).mint(usds.address, usdsUnits("22.0"), 0); - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - // Anna deposits USDT, 6 decimals - await usdt.connect(anna).approve(vault.address, usdtUnits("20.0")); - await vault.connect(anna).mint(usdt.address, usdtUnits("20.0"), 0); - - // Set a Vault buffer and allocate - await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); - await vault.allocate(); - - // Verify 80% went to Compound - await expect( - await compoundStrategy.checkBalance(usds.address) - ).to.approxEqual(usdsUnits("199.8")); - - await expect( - await compoundStrategy.checkBalance(usdc.address) - ).to.approxEqual(usdcUnits("7.2")); - - await expect( - await compoundStrategy.checkBalance(usdt.address) - ).to.approxEqual(usdtUnits("18")); - - expect(await vault.totalValue()).to.approxEqual( - utils.parseUnits("250", 18) - ); - }); - it("Should allow transfer of arbitrary token by Governor", async () => { const { vault, compoundStrategy, ousd, usdc, matt, governor } = fixture; @@ -587,143 +355,37 @@ describe("Vault with Compound strategy", function () { }); it("Should have correct balances on consecutive mint and redeem", async () => { - const { ousd, vault, usdc, usds, anna, matt, josh } = fixture; - - const usersWithBalances = [ - [anna, 0], - [matt, 100], - [josh, 100], - ]; + const { ousd, vault, usdc, anna, matt, josh, governor } = fixture; - const assetsWithUnits = [ - [usds, usdsUnits], - [usdc, usdcUnits], + const testCases = [ + { user: anna, start: 0 }, + { user: matt, start: 100 }, + { user: josh, start: 100 }, ]; - - for (const [user, startBalance] of usersWithBalances) { - for (const [asset, units] of assetsWithUnits) { - for (const amount of [5.09, 10.32, 20.99, 100.01]) { - await asset - .connect(user) - .approve(vault.address, await units(amount.toString())); - await vault - .connect(user) - .mint(asset.address, await units(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - (startBalance + amount).toString(), - ousd - ); - await vault.connect(user).redeem(ousdUnits(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - startBalance.toString(), - ousd - ); - } + const amounts = [5.09, 10.32, 20.99, 100.01]; + + for (const { user, start } of testCases) { + for (const amount of amounts) { + const mintAmount = usdcUnits(amount.toString()); + await usdc.connect(user).approve(vault.address, mintAmount); + await vault.connect(user).mint(usdc.address, mintAmount, 0); + await expect(user).has.an.approxBalanceOf( + (start + amount).toString(), + ousd + ); + await vault.connect(governor).setStrategistAddr(user.address); + await vault + .connect(user) + .requestWithdrawal(ousdUnits(amount.toString())); + await expect(user).has.an.approxBalanceOf(start.toString(), ousd); } } }); - it("Should collect reward tokens and swap via Uniswap", async () => { - const { anna, vault, harvester, governor, compoundStrategy, comp, usdt } = - fixture; - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - - await harvester.connect(governor).setRewardTokenConfig( - comp.address, // reward token - { - allowedSlippageBps: 0, - harvestRewardBps: 100, - swapPlatformAddr: mockUniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[comp.address, usdt.address]] - ) - ); - - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - - // Make sure the Strategy has COMP balance - expect(await comp.balanceOf(await governor.getAddress())).to.be.equal("0"); - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - compAmount - ); - - const balanceBeforeAnna = await usdt.balanceOf(anna.address); - - // prettier-ignore - await harvester - .connect(anna)["harvestAndSwap(address)"](compoundStrategy.address); - - const balanceAfterAnna = await usdt.balanceOf(anna.address); - - // Make sure Vault has 100 USDT balance (the Uniswap mock converts at 1:1) - await expect(vault).has.a.balanceOf("99", usdt); - - // No COMP in Harvester or Compound strategy - await expect(harvester).has.a.balanceOf("0", comp); - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal("0"); - expect(balanceAfterAnna - balanceBeforeAnna).to.be.equal( - utils.parseUnits("1", 6) - ); - }); - - it("Should not swap if slippage is too high", async () => { - const { josh, vault, harvester, governor, compoundStrategy, comp, usdt } = - fixture; - - const mockUniswapRouter = await ethers.getContract("MockUniswapRouter"); - - // Mock router gives 1:1, if we set this to something high there will be - // too much slippage - await setOracleTokenPriceUsd("COMP", "1.3"); - - const compAmount = utils.parseUnits("100", 18); - await comp.connect(governor).mint(compAmount); - await comp.connect(governor).transfer(compoundStrategy.address, compAmount); - await mockUniswapRouter.setSlippage(utils.parseEther("0.75")); - - await harvester.connect(governor).setRewardTokenConfig( - comp.address, // reward token - { - allowedSlippageBps: 0, - harvestRewardBps: 100, - swapPlatformAddr: mockUniswapRouter.address, - doSwapRewardToken: true, - swapPlatform: 0, - liquidationLimit: 0, - }, - utils.defaultAbiCoder.encode( - ["address[]"], - [[comp.address, usdt.address]] - ) - ); - // Make sure Vault has 0 USDT balance - await expect(vault).has.a.balanceOf("0", usdt); - - // Make sure the Strategy has COMP balance - expect(await comp.balanceOf(await governor.getAddress())).to.be.equal("0"); - expect(await comp.balanceOf(compoundStrategy.address)).to.be.equal( - compAmount - ); - - // prettier-ignore - await expect(harvester - .connect(josh)["harvestAndSwap(address)"](compoundStrategy.address)).to.be.revertedWith("Slippage error"); - }); - const mintDoesAllocate = async (amount) => { - const { anna, vault, usdc, governor } = fixture; + const { anna, vault, usdc, governor, compoundStrategy } = fixture; + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.connect(governor).setVaultBuffer(0); await vault.allocate(); await usdc.connect(anna).mint(usdcUnits(amount)); @@ -743,8 +405,9 @@ describe("Vault with Compound strategy", function () { }); it("Alloc with both threshold and buffer", async () => { - const { anna, vault, usdc, usds, governor } = fixture; + const { anna, vault, usdc, usds, governor, compoundStrategy } = fixture; + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); await vault.allocate(); await vault.connect(governor).setVaultBuffer(utils.parseUnits("1", 17)); await vault.connect(governor).setAutoAllocateThreshold(ousdUnits("3")); @@ -758,11 +421,9 @@ describe("Vault with Compound strategy", function () { // 5 should be below the 10% vault buffer (4/204 * 100 = 1.96%) // All funds should remain in vault - await expect(await usdc.balanceOf(vault.address)).to.equal( - usdcUnits(amount) - ); + expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits(amount)); // USDS was allocated before the vault buffer was set - await expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("0")); + expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("0")); // Use an amount above the vault buffer size that will trigger an allocate const allocAmount = "5000"; diff --git a/contracts/test/vault/exchangeRate.js b/contracts/test/vault/exchangeRate.js deleted file mode 100644 index 037d3d1725..0000000000 --- a/contracts/test/vault/exchangeRate.js +++ /dev/null @@ -1,262 +0,0 @@ -const { expect } = require("chai"); - -const { loadDefaultFixture } = require("../_fixture"); -const { - ousdUnits, - usdsUnits, - advanceTime, - setOracleTokenPriceUsd, - isFork, -} = require("../helpers"); - -describe("Vault Redeem", function () { - if (isFork) { - this.timeout(0); - } - - let fixture; - beforeEach(async function () { - fixture = await loadDefaultFixture(); - const { vault, reth, governor } = fixture; - await vault.connect(governor).supportAsset(reth.address, 1); - await setOracleTokenPriceUsd("RETHETH", "1.2"); - }); - - it("Should mint at a positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(anna).has.a.balanceOf("4.80", ousd); - }); - - it("Should mint less at low oracle, positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "1.199"); - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(anna).has.a.approxBalanceOf("4.796", ousd); - }); - - it("Should revert mint at too low oracle, positive exchange rate", async () => { - const { vault, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "1.00"); - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - const tx = vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(tx).to.be.revertedWith("Asset price below peg"); - }); - - it("Should mint same at high oracle, positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "1.2"); - await reth.connect(anna).mint(usdsUnits("4.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("4.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("4.0"), 0); - await expect(anna).has.a.balanceOf("4.80", ousd); - }); - - it("Should rebase at a positive exchange rate", async () => { - const { ousd, vault, reth, anna } = fixture; - await vault.rebase(); - - const beforeGift = await ousd.totalSupply(); - - await reth.connect(anna).mint(usdsUnits("3.0")); - await reth.connect(anna).transfer(vault.address, usdsUnits("3.0")); - - await advanceTime(7 * 24 * 60 * 60); - await vault.rebase(); - - const afterGift = await ousd.totalSupply(); - expect(afterGift.sub(beforeGift)).to.approxEqualTolerance( - ousdUnits("3.6"), - 0.1, - "afterGift" - ); - - await setOracleTokenPriceUsd("RETHETH", "1.4"); - await reth.setExchangeRate(usdsUnits("1.4")); - await advanceTime(7 * 24 * 60 * 60); - await vault.rebase(); - const afterExchangeUp = await ousd.totalSupply(); - - expect(afterExchangeUp.sub(afterGift)).to.approxEqualTolerance( - ousdUnits("0.6"), - 0.1, - "afterExchangeUp" - ); - }); - - it("Should redeem at the expected rate", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - await vault.connect(anna).redeem(usdsUnits("200.0"), 0); - await expect(anna).has.a.balanceOf("50", reth, "RETH"); - await expect(anna).has.a.balanceOf("1100", usds, "USDC"); - }); - - it("Should redeem less at a high oracle", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - // Contains 100 rETH, (200 units) and 200 USDS (200 units) - // After Oracles $600 + $200 = $800 - // - // Redeeming $200 == 1/4 vault - // 25rETH and 50 USDS - - await setOracleTokenPriceUsd("RETHETH", "6.0"); - await reth.setExchangeRate(usdsUnits("6.0")); - await vault.connect(anna).redeem(usdsUnits("200.0"), 0); - await expect(anna).has.a.balanceOf("25", reth, "RETH"); - await expect(anna).has.a.balanceOf("1050", usds, "USDC"); - }); - - it("Should redeem same at a low oracle", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - // Contains 100 rETH, (200 units) and 200 USDS (200 units) - // After Oracles $154 + $200 = $354 - // - // But since the exchange rate is still 2.0 the RETH unit price - // is snapped back to 2.0 when redeeming. Making the calculation: - // After Oracles $200 + $200 = $400 - // - // And redeeming 200 is 50% of the vault = 50 RETH & 100 USDS - - await setOracleTokenPriceUsd("RETHETH", "1.54"); - await vault.connect(anna).redeem(usdsUnits("200.0"), 0); - await expect(anna).has.a.balanceOf("50", reth, "RETH"); - await expect(anna).has.a.balanceOf("1100", usds, "USDC"); - }); - - it("Should redeem same at a low oracle v2", async () => { - const { ousd, vault, usds, reth, anna } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - await reth.connect(anna).mint(usdsUnits("100.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("100.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("200", ousd, "post mint"); - await vault.rebase(); - await expect(anna).has.a.balanceOf("200", ousd, "post rebase"); - - // Contains 100 rETH, (200 units) and 200 USDS (200 units) - // After Oracles $100 + $200 = $300 - // - // Redeeming $150 == 1/2 vault - // 50rETH and 100 USDS - - await setOracleTokenPriceUsd("RETHETH", "1.0"); - await reth.setExchangeRate(usdsUnits("1.0")); - - await vault.connect(anna).redeem(usdsUnits("150.0"), 0); - await expect(anna).has.a.approxBalanceOf("50", reth, "RETH"); - await expect(anna).has.a.approxBalanceOf("1100", usds, "USDC"); - }); - - it("Should handle an exchange rate reedem attack", async () => { - const { ousd, vault, reth, anna, matt, governor } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - // Old holder with RETH - await reth.connect(matt).mint(usdsUnits("500.0")); - await reth.connect(matt).approve(vault.address, usdsUnits("500.0")); - await vault.connect(matt).mint(reth.address, usdsUnits("500.0"), 0); - - // Attacker Mints before exchange change - await reth.connect(anna).mint(usdsUnits("500.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("500.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("500.0"), 0); - await expect(anna).has.a.balanceOf("1000", ousd, "post mint"); - - await setOracleTokenPriceUsd("RETHETH", "1.0"); - await reth.setExchangeRate(usdsUnits("1.0")); - - // console.log("----"); - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - - // Attacker redeems after exchange change - await vault.connect(governor).setMaxSupplyDiff(usdsUnits("0.9")); - await expect( - vault.connect(anna).redeem(usdsUnits("1000.0"), 0) - ).to.be.revertedWith("Backing supply liquidity error"); - - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - }); - - it("Should handle an exchange rate reedem attack, delayed oracle", async () => { - const { ousd, vault, reth, anna, matt, governor } = fixture; - - await setOracleTokenPriceUsd("RETHETH", "2.0"); - await reth.setExchangeRate(usdsUnits("2.0")); - - // Old holder with RETH - await reth.connect(matt).mint(usdsUnits("500.0")); - await reth.connect(matt).approve(vault.address, usdsUnits("500.0")); - await vault.connect(matt).mint(reth.address, usdsUnits("500.0"), 0); - - // Attacker Mints before exchange change - await reth.connect(anna).mint(usdsUnits("500.0")); - await reth.connect(anna).approve(vault.address, usdsUnits("500.0")); - await vault.connect(anna).mint(reth.address, usdsUnits("500.0"), 0); - await expect(anna).has.a.balanceOf("1000", ousd, "post mint"); - - await setOracleTokenPriceUsd("RETHETH", "1.3"); - await reth.setExchangeRate(usdsUnits("1.0")); - - // console.log("----"); - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - - // Attacker redeems after exchange change - await vault.connect(governor).setMaxSupplyDiff(usdsUnits("0.9")); - await expect( - vault.connect(anna).redeem(usdsUnits("1000.0"), 0) - ).to.be.revertedWith("Backing supply liquidity error"); - - // console.log((await vault.totalValue()).toString() / 1e18); - // console.log((await ousd.totalSupply()).toString() / 1e18); - }); -}); diff --git a/contracts/test/vault/harvester.mainnet.fork-test.js b/contracts/test/vault/harvester.mainnet.fork-test.js index ef6818f804..1b6d0887c5 100644 --- a/contracts/test/vault/harvester.mainnet.fork-test.js +++ b/contracts/test/vault/harvester.mainnet.fork-test.js @@ -3,7 +3,6 @@ const { utils, BigNumber } = require("ethers"); const { createFixtureLoader, harvesterFixture } = require("./../_fixture"); const { isCI, oethUnits } = require("./../helpers"); -const { hotDeployOption } = require("../_hot-deploy"); const addresses = require("../../utils/addresses"); const { setERC20TokenBalance } = require("../_fund"); const { parseUnits } = require("ethers").utils; @@ -19,12 +18,6 @@ describe("ForkTest: Harvester", function () { let fixture; beforeEach(async () => { fixture = await loadFixture(); - await hotDeployOption(fixture, null, { - isOethFixture: true, - }); - await hotDeployOption(fixture, null, { - isOethFixture: false, - }); }); // Skipping this since we switched to simple harvester diff --git a/contracts/test/vault/index.js b/contracts/test/vault/index.js index 8c0728ef9e..a8811dfcb6 100644 --- a/contracts/test/vault/index.js +++ b/contracts/test/vault/index.js @@ -1,18 +1,8 @@ const { expect } = require("chai"); -const hre = require("hardhat"); const { utils } = require("ethers"); const { loadDefaultFixture } = require("../_fixture"); -const { - ousdUnits, - usdsUnits, - usdcUnits, - usdtUnits, - tusdUnits, - setOracleTokenPriceUsd, - getOracleAddresses, - isFork, -} = require("../helpers"); +const { ousdUnits, usdsUnits, usdcUnits, isFork } = require("../helpers"); describe("Vault", function () { if (isFork) { @@ -21,52 +11,16 @@ describe("Vault", function () { let fixture; beforeEach(async () => { fixture = await loadDefaultFixture(); + await fixture.compoundStrategy + .connect(fixture.governor) + .setPTokenAddress(fixture.usdc.address, fixture.cusdc.address); }); it("Should support an asset", async () => { - const { vault, oracleRouter, ousd, governor } = fixture; - - const oracleAddresses = await getOracleAddresses(hre.deployments); - const origAssetCount = await vault.connect(governor).getAssetCount(); - expect(await vault.isSupportedAsset(ousd.address)).to.be.false; - - /* Mock oracle feeds report 0 for updatedAt data point. Set - * maxStaleness to 100 years from epoch to make the Oracle - * feeds valid - */ - const maxStaleness = 24 * 60 * 60 * 365 * 100; - - await oracleRouter.setFeed( - ousd.address, - oracleAddresses.chainlink.USDS_USD, - maxStaleness - ); - await oracleRouter.cacheDecimals(ousd.address); - await expect(vault.connect(governor).supportAsset(ousd.address, 0)).to.emit( - vault, - "AssetSupported" - ); - expect(await vault.getAssetCount()).to.equal(origAssetCount.add(1)); - const assets = await vault.connect(governor).getAllAssets(); - expect(assets.length).to.equal(origAssetCount.add(1)); - expect(await vault["checkBalance(address)"](ousd.address)).to.equal(0); - expect(await vault.isSupportedAsset(ousd.address)).to.be.true; - }); - - it("Should revert when adding an asset that is already supported", async function () { - const { vault, usdt, governor } = fixture; + const { vault, usdc, usds } = fixture; - expect(await vault.isSupportedAsset(usdt.address)).to.be.true; - await expect( - vault.connect(governor).supportAsset(usdt.address, 0) - ).to.be.revertedWith("Asset already supported"); - }); - - it("Should revert when attempting to support an asset and not governor", async function () { - const { vault, usdt } = fixture; - await expect(vault.supportAsset(usdt.address, 0)).to.be.revertedWith( - "Caller is not the Governor" - ); + expect(await vault.isSupportedAsset(usds.address)).to.be.false; + expect(await vault.isSupportedAsset(usdc.address)).to.be.true; }); it("Should revert when adding a strategy that is already approved", async function () { @@ -87,111 +41,22 @@ describe("Vault", function () { }); it("Should correctly ratio deposited currencies of differing decimals", async function () { - const { ousd, vault, usdc, usds, matt } = fixture; - + const { ousd, vault, usdc, matt } = fixture; await expect(matt).has.a.balanceOf("100.00", ousd); // Matt deposits USDC, 6 decimals await usdc.connect(matt).approve(vault.address, usdcUnits("2.0")); await vault.connect(matt).mint(usdc.address, usdcUnits("2.0"), 0); await expect(matt).has.a.balanceOf("102.00", ousd); - - // Matt deposits USDS, 18 decimals - await usds.connect(matt).approve(vault.address, usdsUnits("4.0")); - await vault.connect(matt).mint(usds.address, usdsUnits("4.0"), 0); - await expect(matt).has.a.balanceOf("106.00", ousd); - }); - - it("Should correctly handle a deposit of USDS (18 decimals)", async function () { - const { ousd, vault, usds, anna } = fixture; - - await expect(anna).has.a.balanceOf("0.00", ousd); - // We limit to paying to $1 OUSD for for one stable coin, - // so this will deposit at a rate of $1. - await setOracleTokenPriceUsd("USDS", "1.30"); - await usds.connect(anna).approve(vault.address, usdsUnits("3.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("3.0"), 0); - await expect(anna).has.a.balanceOf("3.00", ousd); }); it("Should correctly handle a deposit of USDC (6 decimals)", async function () { const { ousd, vault, usdc, anna } = fixture; await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", "0.998"); await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("49.90", ousd); - }); - - it("Should not allow a below peg deposit", async function () { - const { ousd, vault, usdc, anna } = fixture; - - await expect(anna).has.a.balanceOf("0.00", ousd); - await setOracleTokenPriceUsd("USDC", "0.95"); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await expect( - vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0) - ).to.be.revertedWith("Asset price below peg"); - }); - - it("Should correctly handle a deposit failure of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = - fixture; - - await oracleRouter.cacheDecimals(nonStandardToken.address); - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); - await setOracleTokenPriceUsd("NonStandardToken", "1.30"); - await nonStandardToken - .connect(anna) - .approve(vault.address, usdtUnits("1500.0")); - - // Anna has a balance of 1000 tokens and she is trying to - // transfer 1500 tokens. The contract doesn't throw but - // fails silently, so Anna's OUSD balance should be zero. - try { - await vault - .connect(anna) - .mint(nonStandardToken.address, usdtUnits("1500.0"), 0); - } catch (err) { - expect( - /reverted with reason string 'SafeERC20: ERC20 operation did not succeed/gi.test( - err.message - ) - ).to.be.true; - } finally { - // Make sure nothing got affected - await expect(anna).has.a.balanceOf("0.00", ousd); - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); - } - }); - - it("Should correctly handle a deposit of Non-Standard ERC20 Token", async function () { - const { ousd, vault, anna, nonStandardToken, oracleRouter, governor } = - fixture; - - await oracleRouter.cacheDecimals(nonStandardToken.address); - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); - - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); - - await nonStandardToken - .connect(anna) - .approve(vault.address, usdtUnits("100.0")); - await vault - .connect(anna) - .mint(nonStandardToken.address, usdtUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); - await expect(anna).has.a.balanceOf("900.00", nonStandardToken); - }); - - it("Should calculate the balance correctly with USDS", async () => { - const { vault } = fixture; - - // Vault already has USDS from default ficture - expect(await vault.totalValue()).to.equal(utils.parseUnits("200", 18)); + await expect(anna).has.a.balanceOf("50.00", ousd); }); it("Should calculate the balance correctly with USDC", async () => { @@ -204,46 +69,6 @@ describe("Vault", function () { expect(await vault.totalValue()).to.equal(utils.parseUnits("202", 18)); }); - it("Should calculate the balance correctly with USDT", async () => { - const { vault, usdt, matt } = fixture; - - // Matt deposits USDT, 6 decimals - await usdt.connect(matt).approve(vault.address, usdtUnits("5.0")); - await vault.connect(matt).mint(usdt.address, usdtUnits("5.0"), 0); - // Fixture loads 200 USDS, so result should be 205 - expect(await vault.totalValue()).to.equal(utils.parseUnits("205", 18)); - }); - - it("Should calculate the balance correctly with TUSD", async () => { - const { vault, tusd, matt } = fixture; - - await tusd.connect(matt).mint(ousdUnits("100.0")); - - // Matt deposits TUSD, 18 decimals - await tusd.connect(matt).approve(vault.address, tusdUnits("9.0")); - await vault.connect(matt).mint(tusd.address, tusdUnits("9.0"), 0); - // Fixture loads 200 USDS, so result should be 209 - expect(await vault.totalValue()).to.equal(utils.parseUnits("209", 18)); - }); - - it("Should calculate the balance correctly with USDS, USDC, USDT, TUSD", async () => { - const { vault, usdc, usdt, tusd, matt } = fixture; - - await tusd.connect(matt).mint(ousdUnits("100.0")); - - // Matt deposits USDC, 6 decimals - await usdc.connect(matt).approve(vault.address, usdcUnits("8.0")); - await vault.connect(matt).mint(usdc.address, usdcUnits("8.0"), 0); - // Matt deposits USDT, 6 decimals - await usdt.connect(matt).approve(vault.address, usdtUnits("20.0")); - await vault.connect(matt).mint(usdt.address, usdtUnits("20.0"), 0); - // Matt deposits TUSD, 18 decimals - await tusd.connect(matt).approve(vault.address, tusdUnits("9.0")); - await vault.connect(matt).mint(tusd.address, tusdUnits("9.0"), 0); - // Fixture loads 200 USDS, so result should be 237 - expect(await vault.totalValue()).to.equal(utils.parseUnits("237", 18)); - }); - it("Should allow transfer of arbitrary token by Governor", async () => { const { vault, ousd, usdc, matt, governor } = fixture; @@ -274,7 +99,7 @@ describe("Vault", function () { // Governor cannot move USDC because it is a supported token. await expect( vault.connect(governor).transferToken(usdc.address, ousdUnits("8.0")) - ).to.be.revertedWith("Only unsupported assets"); + ).to.be.revertedWith("Only unsupported asset"); }); it("Should allow Governor to add Strategy", async () => { @@ -305,20 +130,6 @@ describe("Vault", function () { await expect(matt).has.a.balanceOf("100.00", ousd); }); - it("Should revert mint if minMintAmount check fails", async () => { - const { vault, matt, ousd, usds, usdt } = fixture; - - await usdt.connect(matt).approve(vault.address, usdtUnits("50.0")); - await usds.connect(matt).approve(vault.address, usdsUnits("25.0")); - - await expect( - vault.connect(matt).mint(usdt.address, usdtUnits("50"), usdsUnits("100")) - ).to.be.revertedWith("Mint amount lower than minimum"); - - await expect(matt).has.a.balanceOf("100.00", ousd); - expect(await ousd.totalSupply()).to.eq(ousdUnits("200.0")); - }); - it("Should allow transfer of arbitrary token by Governor", async () => { const { vault, ousd, usdc, matt, governor } = fixture; @@ -370,61 +181,57 @@ describe("Vault", function () { }); it("Should allow the Governor to call withdraw and then deposit", async () => { - const { vault, governor, usds, josh, compoundStrategy } = fixture; + const { vault, governor, usdc, josh, compoundStrategy } = fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Send all USDS to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); + // Send all USDC to Compound + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(josh).approve(vault.address, usdcUnits("200")); + await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); await vault .connect(governor) .withdrawFromStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); await vault .connect(governor) .depositToStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); }); it("Should allow the Strategist to call withdrawFromStrategy and then depositToStrategy", async () => { - const { vault, governor, usds, josh, strategist, compoundStrategy } = + const { vault, governor, usdc, josh, strategist, compoundStrategy } = fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Send all USDS to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); + // Send all USDC to Compound + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(josh).approve(vault.address, usdcUnits("200")); + await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); await vault .connect(strategist) .withdrawFromStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); await vault .connect(strategist) .depositToStrategy( compoundStrategy.address, - [usds.address], - [usdsUnits("200")] + [usdc.address], + [usdcUnits("200")] ); }); @@ -449,35 +256,14 @@ describe("Vault", function () { }); it("Should withdrawFromStrategy the correct amount for multiple assests and redeploy them using depositToStrategy", async () => { - const { - vault, - governor, - usds, - usdc, - cusdc, - josh, - strategist, - compoundStrategy, - } = fixture; + const { vault, governor, usdc, josh, strategist, compoundStrategy } = + fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Send all USDS to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - - // Add USDC - await compoundStrategy - .connect(governor) - .setPTokenAddress(usdc.address, cusdc.address); // Send all USDC to Compound - await vault - .connect(governor) - .setAssetDefaultStrategy(usdc.address, compoundStrategy.address); + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); await usdc.connect(josh).approve(vault.address, usdcUnits("90")); await vault.connect(josh).mint(usdc.address, usdcUnits("90"), 0); await vault.connect(governor).allocate(); @@ -486,15 +272,11 @@ describe("Vault", function () { .connect(strategist) .withdrawFromStrategy( compoundStrategy.address, - [usds.address, usdc.address], - [usdsUnits("50"), usdcUnits("90")] + [usdc.address], + [usdcUnits("90")] ); // correct balances at the end - const expectedVaultUsdsBalance = usdsUnits("50"); - expect(await usds.balanceOf(vault.address)).to.equal( - expectedVaultUsdsBalance - ); const expectedVaultUsdcBalance = usdcUnits("90"); expect(await usdc.balanceOf(vault.address)).to.equal( expectedVaultUsdcBalance @@ -504,12 +286,11 @@ describe("Vault", function () { .connect(strategist) .depositToStrategy( compoundStrategy.address, - [usds.address, usdc.address], - [usdsUnits("50"), usdcUnits("90")] + [usdc.address], + [usdcUnits("90")] ); // correct balances after depositing back - expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("0")); expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits("0")); }); @@ -544,19 +325,17 @@ describe("Vault", function () { }); it("Should only allow Governor and Strategist to call withdrawAllFromStrategy", async () => { - const { vault, governor, strategist, compoundStrategy, matt, josh, usds } = + const { vault, governor, strategist, compoundStrategy, matt, josh, usdc } = fixture; await vault.connect(governor).approveStrategy(compoundStrategy.address); - // Get the vault's initial USDS balance. - const vaultUsdsBalance = await usds.balanceOf(vault.address); + // Get the vault's initial USDC balance. + const vaultUsdcBalance = await usdc.balanceOf(vault.address); - // Mint and allocate USDS to Compound. - await vault - .connect(governor) - .setAssetDefaultStrategy(usds.address, compoundStrategy.address); - await usds.connect(josh).approve(vault.address, usdsUnits("200")); - await vault.connect(josh).mint(usds.address, usdsUnits("200"), 0); + // Mint and allocate USDC to Compound. + await vault.connect(governor).setDefaultStrategy(compoundStrategy.address); + await usdc.connect(josh).approve(vault.address, usdcUnits("200")); + await vault.connect(josh).mint(usdc.address, usdcUnits("200"), 0); await vault.connect(governor).allocate(); // Call to withdrawAll by the governor should go thru. @@ -564,9 +343,9 @@ describe("Vault", function () { .connect(governor) .withdrawAllFromStrategy(compoundStrategy.address); - // All the USDS should have been moved back to the vault. - const expectedVaultUsdsBalance = vaultUsdsBalance.add(usdsUnits("200")); - await expect(await usds.balanceOf(vault.address)).to.equal( + // All the USDC should have been moved back to the vault. + const expectedVaultUsdsBalance = vaultUsdcBalance.add(usdcUnits("200")); + await expect(await usdc.balanceOf(vault.address)).to.equal( expectedVaultUsdsBalance ); @@ -580,68 +359,4 @@ describe("Vault", function () { vault.connect(matt).withdrawAllFromStrategy(compoundStrategy.address) ).to.be.revertedWith("Caller is not the Strategist or Governor"); }); - - it("Should only allow metastrategy to mint oTokens and revert when threshold is reached.", async () => { - const { vault, ousd, governor, anna, josh } = fixture; - - await vault - .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - // Approve anna address as an address allowed to mint OUSD without backing - await vault.connect(governor).setOusdMetaStrategy(anna.address); - - await expect( - vault.connect(anna).mintForStrategy(ousdUnits("11")) - ).to.be.revertedWith( - "Minted ousd surpassed netOusdMintForStrategyThreshold." - ); - - await expect( - vault.connect(josh).mintForStrategy(ousdUnits("9")) - ).to.be.revertedWith("Caller is not the OUSD meta strategy"); - - await vault.connect(anna).mintForStrategy(ousdUnits("9")); - - await expect(await ousd.balanceOf(anna.address)).to.equal(ousdUnits("9")); - }); - - it("Should reset netOusdMintedForStrategy when new threshold is set", async () => { - const { vault, governor, anna } = fixture; - - await vault - .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - - // Approve anna address as an address allowed to mint OUSD without backing - await vault.connect(governor).setOusdMetaStrategy(anna.address); - await vault.connect(anna).mintForStrategy(ousdUnits("9")); - - // netOusdMintedForStrategy should be equal to amount minted - await expect(await vault.netOusdMintedForStrategy()).to.equal( - ousdUnits("9") - ); - - await vault - .connect(governor) - .setNetOusdMintForStrategyThreshold(ousdUnits("10")); - - // netOusdMintedForStrategy should be reset back to 0 - await expect(await vault.netOusdMintedForStrategy()).to.equal( - ousdUnits("0") - ); - }); - it("Should re-cache decimals", async () => { - const { vault, governor, usdc } = fixture; - - const beforeAssetConfig = await vault.getAssetConfig(usdc.address); - expect(beforeAssetConfig.decimals).to.equal(6); - - // cacheDecimals is not on IVault so we need to use the admin contract - const vaultAdmin = await ethers.getContractAt("VaultAdmin", vault.address); - - await vaultAdmin.connect(governor).cacheDecimals(usdc.address); - - const afterAssetConfig = await vault.getAssetConfig(usdc.address); - expect(afterAssetConfig.decimals).to.equal(6); - }); }); diff --git a/contracts/test/vault/oeth-vault.js b/contracts/test/vault/oeth-vault.js index a9056f88ac..9953062cb9 100644 --- a/contracts/test/vault/oeth-vault.js +++ b/contracts/test/vault/oeth-vault.js @@ -1,5 +1,4 @@ const { expect } = require("chai"); -const hre = require("hardhat"); const { createFixtureLoader, oethDefaultFixture } = require("../_fixture"); const { parseUnits } = require("ethers/lib/utils"); @@ -115,20 +114,6 @@ describe("OETH Vault", function () { ); }); - it("Fail to mint with any other asset", async () => { - const { oethVault, frxETH, stETH, reth, josh } = fixture; - - const amount = parseUnits("1", 18); - const minOeth = parseUnits("0.8", 18); - - for (const asset of [frxETH, stETH, reth]) { - await asset.connect(josh).approve(oethVault.address, amount); - const tx = oethVault.connect(josh).mint(asset.address, amount, minOeth); - - await expect(tx).to.be.revertedWith("Unsupported asset for minting"); - } - }); - it("Fail to mint if amount is zero", async () => { const { oethVault, weth, josh } = fixture; @@ -155,7 +140,7 @@ describe("OETH Vault", function () { await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); const fixtureWithUser = { ...fixture, user: domen }; const dataBefore = await snapData(fixtureWithUser); @@ -189,38 +174,8 @@ describe("OETH Vault", function () { }); }); - describe("Redeem", () => { - it("Should return only WETH in redeem calculations", async () => { - const { oethVault, weth } = fixture; - - const outputs = await oethVault.calculateRedeemOutputs( - oethUnits("1234.43") - ); - - const assets = await oethVault.getAllAssets(); - - expect(assets.length).to.equal(outputs.length); - - for (let i = 0; i < assets.length; i++) { - expect(outputs[i]).to.equal( - assets[i] == weth.address ? oethUnits("1234.43") : "0" - ); - } - }); - - it("Fail to calculateRedeemOutputs if WETH index isn't cached", async () => { - const { frxETH, weth } = fixture; - - await deployWithConfirmation("MockOETHVault", [weth.address]); - const mockVault = await hre.ethers.getContract("MockOETHVault"); - - await mockVault.supportAsset(frxETH.address); - - const tx = mockVault.calculateRedeemOutputs(oethUnits("12343")); - await expect(tx).to.be.revertedWith("WETH Asset index not cached"); - }); - - it("Should update total supply correctly without redeem fee", async () => { + describe("async withdrawal", () => { + it("Should update total supply correctly", async () => { const { oethVault, oeth, weth, daniel } = fixture; await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); @@ -228,7 +183,9 @@ describe("OETH Vault", function () { const vaultBalanceBefore = await weth.balanceOf(oethVault.address); const supplyBefore = await oeth.totalSupply(); - await oethVault.connect(daniel).redeem(oethUnits("10"), "0"); + await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); + await advanceTime(10 * 60); // 10 minutes + await oethVault.connect(daniel).claimWithdrawal(0); const userBalanceAfter = await weth.balanceOf(daniel.address); const vaultBalanceAfter = await weth.balanceOf(oethVault.address); @@ -240,42 +197,14 @@ describe("OETH Vault", function () { expect(supplyBefore.sub(supplyAfter)).to.eq(oethUnits("10")); }); - it("Should update total supply correctly with redeem fee", async () => { - const { oethVault, oeth, weth, daniel } = fixture; - await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); - - await oethVault - .connect(await impersonateAndFund(await oethVault.governor())) - .setRedeemFeeBps(100); - - const userBalanceBefore = await weth.balanceOf(daniel.address); - const vaultBalanceBefore = await weth.balanceOf(oethVault.address); - const supplyBefore = await oeth.totalSupply(); - - await oethVault.connect(daniel).redeem(oethUnits("10"), "0"); - - const userBalanceAfter = await weth.balanceOf(daniel.address); - const vaultBalanceAfter = await weth.balanceOf(oethVault.address); - const supplyAfter = await oeth.totalSupply(); - - // Make sure the total supply went down - expect(userBalanceAfter.sub(userBalanceBefore)).to.eq( - oethUnits("10").sub(oethUnits("0.1")) - ); - expect(vaultBalanceBefore.sub(vaultBalanceAfter)).to.eq( - oethUnits("10").sub(oethUnits("0.1")) - ); - expect(supplyBefore.sub(supplyAfter)).to.eq(oethUnits("10")); - }); - - it("Fail to redeem if not enough liquidity available in the vault", async () => { + it("Fail to claim if not enough liquidity available in the vault", async () => { const { oethVault, weth, domen, governor } = fixture; const mockStrategy = await deployWithConfirmation("MockStrategy"); await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint some WETH await weth.connect(domen).approve(oethVault.address, oethUnits("10000")); @@ -285,68 +214,32 @@ describe("OETH Vault", function () { await oethVault.connect(domen).mint(weth.address, oethUnits("1.23"), "0"); // Withdraw something more than what the Vault holds - const tx = oethVault.connect(domen).redeem(oethUnits("12.55"), "0"); + await oethVault.connect(domen).requestWithdrawal(oethUnits("12.55")); + await advanceTime(10 * 60); // 10 minutes - await expect(tx).to.revertedWith("Liquidity error"); + const tx = oethVault.connect(domen).claimWithdrawal(0); + await expect(tx).to.revertedWith("Queue pending liquidity"); }); - it("Should redeem zero amount without revert", async () => { + it("Should request withdrawal of zero amount without revert", async () => { const { oethVault, daniel } = fixture; - await oethVault.connect(daniel).redeem(0, 0); + await oethVault.connect(daniel).requestWithdrawal(0); }); - it("Fail to redeem if not enough liquidity", async () => { - const { oethVault, daniel } = fixture; - const tx = oethVault - .connect(daniel) - .redeem(oethUnits("1023232323232"), "0"); - await expect(tx).to.be.revertedWith("Liquidity error"); - }); - it("Should allow every user to redeem", async () => { + it("Should allow every user to withdraw", async () => { const { oethVault, weth, daniel } = fixture; await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); - await oethVault.connect(daniel).redeem(oethUnits("10"), oethUnits("0")); + await oethVault.connect(daniel).requestWithdrawal(oethUnits("10")); + await advanceTime(10 * 60); // 10 minutes + await oethVault.connect(daniel).claimWithdrawal(0); await expect(await weth.balanceOf(oethVault.address)).to.equal(0); }); }); describe("Config", () => { - it("Should allow caching WETH index", async () => { - const { oethVault, weth, governor } = fixture; - - await oethVault.connect(governor).cacheWETHAssetIndex(); - - const index = (await oethVault.wethAssetIndex()).toNumber(); - - const assets = await oethVault.getAllAssets(); - - expect(assets[index]).to.equal(weth.address); - }); - - it("Fail to allow anyone other than Governor to change cached index", async () => { - const { oethVault, strategist } = fixture; - - const tx = oethVault.connect(strategist).cacheWETHAssetIndex(); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Fail to cacheWETHAssetIndex if WETH is not a supported asset", async () => { - const { frxETH, weth } = fixture; - const { deployerAddr } = await hre.getNamedAccounts(); - const sDeployer = hre.ethers.provider.getSigner(deployerAddr); - - await deployWithConfirmation("MockOETHVault", [weth.address]); - const mockVault = await hre.ethers.getContract("MockOETHVault"); - - await mockVault.supportAsset(frxETH.address); - - const tx = mockVault.connect(sDeployer).cacheWETHAssetIndex(); - await expect(tx).to.be.revertedWith("Invalid WETH Asset Index"); - }); - it("Should return all strategies", async () => { // Mostly to increase coverage @@ -366,78 +259,6 @@ describe("OETH Vault", function () { }); describe("Remove Asset", () => { - it("Should allow removing a single asset", async () => { - const { oethVault, frxETH, governor } = fixture; - - const vaultAdmin = await ethers.getContractAt( - "OETHVaultAdmin", - oethVault.address - ); - const assetCount = (await oethVault.getAssetCount()).toNumber(); - - const tx = await oethVault.connect(governor).removeAsset(frxETH.address); - - await expect(tx) - .to.emit(vaultAdmin, "AssetRemoved") - .withArgs(frxETH.address); - await expect(tx) - .to.emit(vaultAdmin, "AssetDefaultStrategyUpdated") - .withArgs(frxETH.address, addresses.zero); - - expect(await oethVault.isSupportedAsset(frxETH.address)).to.be.false; - expect(await oethVault.checkBalance(frxETH.address)).to.equal(0); - expect(await oethVault.assetDefaultStrategies(frxETH.address)).to.equal( - addresses.zero - ); - - const allAssets = await oethVault.getAllAssets(); - expect(allAssets.length).to.equal(assetCount - 1); - - expect(allAssets).to.not.contain(frxETH.address); - - const config = await oethVault.getAssetConfig(frxETH.address); - expect(config.isSupported).to.be.false; - }); - - it("Should only allow governance to remove assets", async () => { - const { oethVault, weth, strategist, josh } = fixture; - - for (const signer of [strategist, josh]) { - let tx = oethVault.connect(signer).removeAsset(weth.address); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - - tx = oethVault.connect(signer).removeAsset(weth.address); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Fail to remove asset if asset is not supported", async () => { - const { oethVault, usds, governor } = fixture; - const tx = oethVault.connect(governor).removeAsset(usds.address); - - await expect(tx).to.be.revertedWith("Asset not supported"); - }); - - it("Fail to remove asset if vault still holds the asset", async () => { - const { oethVault, weth, governor, daniel } = fixture; - - await oethVault.connect(daniel).mint(weth.address, oethUnits("1"), "0"); - - const tx = oethVault.connect(governor).removeAsset(weth.address); - - await expect(tx).to.be.revertedWith("Vault still holds asset"); - }); - - it("Fail to revert for smaller dust", async () => { - const { oethVault, weth, governor, daniel } = fixture; - - await oethVault.connect(daniel).mint(weth.address, "500000000000", "0"); - - const tx = oethVault.connect(governor).removeAsset(weth.address); - - await expect(tx).to.not.be.revertedWith("Vault still holds asset"); - }); - it("Should allow strategy to burnForStrategy", async () => { const { oethVault, oeth, weth, governor, daniel } = fixture; @@ -446,11 +267,6 @@ describe("OETH Vault", function () { .connect(governor) .addStrategyToMintWhitelist(daniel.address); - // First increase netOusdMintForStrategyThreshold - await oethVault - .connect(governor) - .setNetOusdMintForStrategyThreshold(oethUnits("100")); - // Then mint for strategy await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); @@ -534,7 +350,7 @@ describe("OETH Vault", function () { .approveStrategy(mockStrategy.address); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint will allocate all to default strategy bc no buffer, no threshold await oethVault.connect(daniel).mint(weth.address, oethUnits("10"), "0"); @@ -575,14 +391,14 @@ describe("OETH Vault", function () { let mockStrategy; beforeEach(async () => { // Deploy default strategy - const { oethVault, weth } = fixture; + const { oethVault } = fixture; mockStrategy = await deployWithConfirmation("MockStrategy"); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) .approveStrategy(mockStrategy.address); await oethVault .connect(await impersonateAndFund(await oethVault.governor())) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); }); it("buffer is 0%, 0 WETH in queue", async () => { const { oethVault, daniel, weth } = fixture; @@ -1119,7 +935,7 @@ describe("OETH Vault", function () { [weth.address], [depositAmount] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Fail to deposit allocated WETH during allocate", async () => { const { oethVault, governor, weth } = fixture; @@ -1127,7 +943,7 @@ describe("OETH Vault", function () { // Set mock strategy as default strategy await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // and buffer to 10% await oethVault.connect(governor).setVaultBuffer(oethUnits("0.1")); @@ -1647,7 +1463,7 @@ describe("OETH Vault", function () { [oethUnits("1")] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Fail to allocate any WETH to the default strategy", async () => { const { oethVault, domen } = fixture; @@ -1690,7 +1506,7 @@ describe("OETH Vault", function () { [oethUnits("1.1")] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Fail to allocate any WETH to the default strategy", async () => { const { oethVault, domen } = fixture; @@ -1733,14 +1549,14 @@ describe("OETH Vault", function () { [oethUnits("5")] ); - await expect(tx).to.be.revertedWith("Not enough WETH available"); + await expect(tx).to.be.revertedWith("Not enough assets available"); }); it("Should allocate 3 WETH to the default strategy", async () => { const { oethVault, governor, domen, weth } = fixture; await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); const vaultBalance = await weth.balanceOf(oethVault.address); const stratBalance = await weth.balanceOf(mockStrategy.address); @@ -1965,7 +1781,7 @@ describe("OETH Vault", function () { await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint 60 OETH to three users await oethVault @@ -2125,7 +1941,7 @@ describe("OETH Vault", function () { await oethVault.connect(governor).approveStrategy(mockStrategy.address); await oethVault .connect(governor) - .setAssetDefaultStrategy(weth.address, mockStrategy.address); + .setDefaultStrategy(mockStrategy.address); // Mint 100 OETH to three users await oethVault diff --git a/contracts/test/vault/oeth-vault.mainnet.fork-test.js b/contracts/test/vault/oeth-vault.mainnet.fork-test.js index e797a5492d..4e144a48b3 100644 --- a/contracts/test/vault/oeth-vault.mainnet.fork-test.js +++ b/contracts/test/vault/oeth-vault.mainnet.fork-test.js @@ -49,14 +49,6 @@ describe("ForkTest: OETH Vault", function () { ); } }); - - it("Should have correct WETH asset index cached", async () => { - const { oethVault, weth } = fixture; - const index = await oethVault.wethAssetIndex(); - const assets = await oethVault.getAllAssets(); - - expect(assets[index]).to.equal(weth.address); - }); }); describe("user operations", () => { @@ -105,15 +97,7 @@ describe("ForkTest: OETH Vault", function () { }); it("should mint with WETH and allocate to strategy", async () => { - const { oethVault, nativeStakingSSVStrategy, weth, josh, strategist } = - fixture; - - oethVault - .connect(strategist) - .setAssetDefaultStrategy( - weth.address, - nativeStakingSSVStrategy.address - ); + const { oethVault, weth, josh } = fixture; const amount = parseUnits("11", 18); const minOeth = parseUnits("8", 18); @@ -131,139 +115,26 @@ describe("ForkTest: OETH Vault", function () { .withArgs(josh.address, amount); }); - it("should not mint with any other asset", async () => { - const { oethVault, frxETH, stETH, reth, josh } = fixture; + it("should mint when specifying any other assets", async () => { + const { oethVault, frxETH, stETH, reth, weth, josh } = fixture; const amount = parseUnits("1", 18); const minOeth = parseUnits("0.8", 18); for (const asset of [frxETH, stETH, reth]) { - await asset.connect(josh).approve(oethVault.address, amount); - const tx = oethVault.connect(josh).mint(asset.address, amount, minOeth); - - await expect(tx).to.be.revertedWith("Unsupported asset for minting"); - } - }); - - it("should have redeem fee", async () => { - const { oethVault } = fixture; - - expect(await oethVault.redeemFeeBps()).to.equal(1000); - }); - - it("should return only WETH in redeem calculations", async () => { - const { oethVault } = fixture; - - const output = await oethVault.calculateRedeemOutputs(oethUnits("123")); - const index = await oethVault.wethAssetIndex(); - - expect(output[index]).to.equal(oethUnits("123").mul("9000").div("10000")); - - output.map((x, i) => { - if (i !== index.toNumber()) { - expect(x).to.equal("0"); - } - }); - }); - - it("should allow strategist to redeem without fee", async () => { - const { oethVault, strategist, matt, weth, oeth } = fixture; - - const sGovernor = await impersonateAndFund(addresses.mainnet.Timelock); - // make sure to not trigger rebase on redeem - await oethVault.connect(sGovernor).setRebaseThreshold(oethUnits("11")); - - // Send a heap of WETH to the vault so it can be redeemed - await weth.connect(matt).transfer(oethVault.address, oethUnits("1000")); - await weth.connect(matt).transfer(strategist.address, oethUnits("100")); - - const amount = oethUnits("10"); - - await weth.connect(strategist).approve(oethVault.address, amount); - - // Mint 1:1 - await oethVault.connect(strategist).mint(weth.address, amount, amount); - - const oethBalanceBefore = await oeth.balanceOf(strategist.address); - const wethBalanceBefore = await weth.balanceOf(strategist.address); - - // Redeem 1:1 instantly - await oethVault.connect(strategist).redeem(amount, amount); - - const oethBalanceAfter = await oeth.balanceOf(strategist.address); - const wethBalanceAfter = await weth.balanceOf(strategist.address); - - expect(oethBalanceAfter).to.equal(oethBalanceBefore.sub(amount)); - expect(wethBalanceAfter).to.equal(wethBalanceBefore.add(amount)); - }); - - it("should enforce fee on other users for instant redeem", async () => { - const { oethVault, josh, matt, weth, oeth } = fixture; - - // Send a heap of WETH to the vault so it can be redeemed - await weth.connect(matt).transfer(oethVault.address, oethUnits("1000")); - - const amount = oethUnits("10"); - const expectedWETH = amount.mul("9000").div("10000"); - - await weth.connect(josh).approve(oethVault.address, amount); - - // Mint 1:1 - await oethVault.connect(josh).mint(weth.address, amount, amount); - - const oethBalanceBefore = await oeth.balanceOf(josh.address); - const wethBalanceBefore = await weth.balanceOf(josh.address); + const wethBefore = await weth.balanceOf(josh.address); + const assetBefore = await asset.balanceOf(josh.address); - // Redeem 1:1 instantly - await oethVault.connect(josh).redeem(amount, expectedWETH); + // Mints with WETH even though other assets are specified + await oethVault.connect(josh).mint(asset.address, amount, minOeth); - const oethBalanceAfter = await oeth.balanceOf(josh.address); - const wethBalanceAfter = await weth.balanceOf(josh.address); - - expect(oethBalanceAfter).to.equal(oethBalanceBefore.sub(amount)); - expect(wethBalanceAfter).to.equal(wethBalanceBefore.add(expectedWETH)); - }); - - it("should partially redeem 10 OETH", async () => { - const { domen, oeth, oethVault, weth } = fixture; - - expect(await oeth.balanceOf(oethWhaleAddress)).to.gt(10); - - const redeemAmount = parseUnits("10", 18); - const minEth = parseUnits("9", 18); - - // Calculate how much to mint based on the WETH in the vault, - // the withdrawal queue, and the WETH to be redeemed - const wethBalance = await weth.balanceOf(oethVault.address); - const queue = await oethVault.withdrawalQueueMetadata(); - const available = wethBalance.add(queue.claimed).sub(queue.queued); - const mintAmount = redeemAmount.sub(available); - if (mintAmount.gt(0)) { - await weth.connect(domen).transfer(oethVault.address, mintAmount); + expect(await weth.balanceOf(josh.address)).to.eq( + wethBefore.sub(amount) + ); + expect(await asset.balanceOf(josh.address)).to.eq(assetBefore); } - - const tx = await oethVault - .connect(oethWhaleSigner) - .redeem(redeemAmount, minEth); - - await logTxDetails(tx, "redeem"); - - await expect(tx) - .to.emit(oethVault, "Redeem") - .withNamedArgs({ _addr: oethWhaleAddress }); }); - it.skip("should not do full redeem by OETH whale", async () => { - const { oeth, oethVault } = fixture; - - const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); - expect(oethWhaleBalance, "no longer an OETH whale").to.gt( - parseUnits("1000", 18) - ); - - const tx = oethVault.connect(oethWhaleSigner).redeem(oethWhaleBalance, 0); - await expect(tx).to.revertedWith("Liquidity error"); - }); it("should request a withdraw by OETH whale", async () => { const { oeth, oethVault } = fixture; @@ -287,10 +158,10 @@ describe("ForkTest: OETH Vault", function () { }); }); it("should claim withdraw by a OETH whale", async () => { - const { domen, oeth, oethVault, weth } = fixture; + const { domen, oeth, oethVault, weth, matt } = fixture; let oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); - + await depositDiffInWeth(fixture, matt); // Calculate how much to mint based on the WETH in the vault, // the withdrawal queue, and the WETH to be withdrawn const wethBalance = await weth.balanceOf(oethVault.address); @@ -326,27 +197,30 @@ describe("ForkTest: OETH Vault", function () { .withArgs(oethWhaleAddress, requestId, oethWhaleBalance); }); it("OETH whale can redeem after withdraw from all strategies", async () => { - const { oeth, oethVault, timelock } = fixture; + const { oeth, oethVault, timelock, matt } = fixture; const oethWhaleBalance = await oeth.balanceOf(oethWhaleAddress); log(`OETH whale balance: ${formatUnits(oethWhaleBalance)}`); expect(oethWhaleBalance, "no longer an OETH whale").to.gt( parseUnits("1000", 18) ); + await depositDiffInWeth(fixture, matt); await oethVault.connect(timelock).withdrawAllFromStrategies(); - const tx = await oethVault + const { nextWithdrawalIndex: requestId } = + await oethVault.withdrawalQueueMetadata(); + + await oethVault .connect(oethWhaleSigner) - .redeem(oethWhaleBalance, 0); - await expect(tx) - .to.emit(oethVault, "Redeem") - .withNamedArgs({ _addr: oethWhaleAddress }); + .requestWithdrawal(oethWhaleBalance); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + await await oethVault.connect(oethWhaleSigner).claimWithdrawal(requestId); }); it("Vault should have the right WETH address", async () => { const { oethVault } = fixture; - expect((await oethVault.weth()).toLowerCase()).to.equal( + expect((await oethVault.asset()).toLowerCase()).to.equal( addresses.mainnet.WETH.toLowerCase() ); }); @@ -362,30 +236,25 @@ describe("ForkTest: OETH Vault", function () { }); }); - // We have migrated to simplified Harvester and this is no longer relevant - // shouldHaveRewardTokensConfigured(() => ({ - // vault: fixture.oethVault, - // harvester: fixture.oethHarvester, - // ignoreTokens: [fixture.weth.address.toLowerCase()], - // expectedConfigs: { - // [fixture.cvx.address]: { - // allowedSlippageBps: 300, - // harvestRewardBps: 200, - // swapPlatformAddr: "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4", - // doSwapRewardToken: true, - // swapPlatform: 3, - // liquidationLimit: oethUnits("2500"), - // curvePoolIndices: [1, 0], - // }, - // [fixture.crv.address]: { - // allowedSlippageBps: 300, - // harvestRewardBps: 200, - // swapPlatformAddr: "0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14", - // doSwapRewardToken: true, - // swapPlatform: 3, - // liquidationLimit: oethUnits("4000"), - // curvePoolIndices: [2, 1], - // }, - // }, - // })); + /** + * Checks the difference between withdrawalQueueMetadata[0] and [2] + * and deposits this diff in WETH. + * @param {Object} fixture + * @param {Object} depositor signer to perform the deposit + */ + async function depositDiffInWeth(fixture, depositor) { + const { oethVault, weth } = fixture; + + // Get withdrawalQueueMetadata[0] and [2] + const metadata = await oethVault.withdrawalQueueMetadata(); + const queue = metadata.queued; + const claimed = metadata.claimed; + + if (queue > claimed) { + const diff = queue.sub(claimed).mul(110).div(100); + await weth.connect(depositor).approve(oethVault.address, diff); + return await oethVault.connect(depositor).mint(weth.address, diff, 0); + } + return null; + } }); diff --git a/contracts/test/vault/oethb-vault.base.fork-test.js b/contracts/test/vault/oethb-vault.base.fork-test.js index 2dc81d2782..81972e7bf7 100644 --- a/contracts/test/vault/oethb-vault.base.fork-test.js +++ b/contracts/test/vault/oethb-vault.base.fork-test.js @@ -51,83 +51,6 @@ describe("ForkTest: OETHb Vault", function () { 0.1 ); }); - - it("Should allow only Strategist to redeem", async () => { - const { strategist, oethbVault, oethb, weth, rafael } = fixture; - - // Add WETH liquidity to allow redeem - await weth - .connect(rafael) - .transfer(oethbVault.address, oethUnits("3000")); - - await oethbVault.rebase(); - await _mint(strategist); - - const vaultBalanceBefore = await weth.balanceOf(oethbVault.address); - const userBalanceBefore = await oethb.balanceOf(strategist.address); - const totalSupplyBefore = await oethb.totalSupply(); - - await oethbVault.connect(strategist).redeem(oethUnits("1"), "0"); - - const vaultBalanceAfter = await weth.balanceOf(oethbVault.address); - const userBalanceAfter = await oethb.balanceOf(strategist.address); - const totalSupplyAfter = await oethb.totalSupply(); - - expect(totalSupplyAfter).to.approxEqualTolerance( - totalSupplyBefore.sub(oethUnits("1")) - ); - expect(userBalanceAfter).to.approxEqualTolerance( - userBalanceBefore.sub(oethUnits("1")) - ); - expect(vaultBalanceAfter).to.approxEqualTolerance( - vaultBalanceBefore.sub(oethUnits("1")) - ); - }); - - it("Should allow only Governor to redeem", async () => { - const { governor, oethbVault, oethb, weth, rafael } = fixture; - - // Add WETH liquidity to allow redeem - await weth - .connect(rafael) - .transfer(oethbVault.address, oethUnits("3000")); - - await oethbVault.rebase(); - await _mint(governor); - - const vaultBalanceBefore = await weth.balanceOf(oethbVault.address); - const userBalanceBefore = await oethb.balanceOf(governor.address); - const totalSupplyBefore = await oethb.totalSupply(); - - await oethbVault.connect(governor).redeem(oethUnits("1"), "0"); - - const vaultBalanceAfter = await weth.balanceOf(oethbVault.address); - const userBalanceAfter = await oethb.balanceOf(governor.address); - const totalSupplyAfter = await oethb.totalSupply(); - - expect(totalSupplyAfter).to.approxEqualTolerance( - totalSupplyBefore.sub(oethUnits("1")) - ); - expect(userBalanceAfter).to.approxEqualTolerance( - userBalanceBefore.sub(oethUnits("1")) - ); - expect(vaultBalanceAfter).to.approxEqualTolerance( - vaultBalanceBefore.sub(oethUnits("1")) - ); - }); - - it("No one else can redeem", async () => { - const { rafael, nick, oethbVault } = fixture; - - await oethbVault.rebase(); - - for (const signer of [rafael, nick]) { - await _mint(signer); - await expect( - oethbVault.connect(signer).redeem(oethUnits("1"), "0") - ).to.be.revertedWith("Caller is not the Strategist or Governor"); - } - }); }); describe("Async withdrawals", function () { @@ -137,7 +60,10 @@ describe("ForkTest: OETHb Vault", function () { // Add WETH liquidity to allow withdrawal await weth .connect(rafael) - .transfer(oethbVault.address, oethUnits("3000")); + .approve(oethbVault.address, oethUnits("10000")); + await oethbVault + .connect(rafael) + .mint(weth.address, oethUnits("10000"), 0); const delayPeriod = await oethbVault.withdrawalClaimDelay(); diff --git a/contracts/test/vault/oethb-vault.base.js b/contracts/test/vault/oethb-vault.base.js index 75c55aa396..41c74e8cef 100644 --- a/contracts/test/vault/oethb-vault.base.js +++ b/contracts/test/vault/oethb-vault.base.js @@ -96,17 +96,6 @@ describe("OETHb Vault", function () { .removeStrategyFromMintWhitelist(addresses.dead); await expect(tx).to.be.revertedWith("Not whitelisted"); }); - - describe("Disabled functions", function () { - it("Should not support redeem", async () => { - const { oethbVault, nick } = fixture; - - const tx = oethbVault.connect(nick).redeem(1, 0); - await expect(tx).to.be.revertedWith( - "Caller is not the Strategist or Governor" - ); - }); - }); }); describe("Mint & Burn For Strategy", function () { diff --git a/contracts/test/vault/oneinch-swapper.js b/contracts/test/vault/oneinch-swapper.js deleted file mode 100644 index f8ae6466d6..0000000000 --- a/contracts/test/vault/oneinch-swapper.js +++ /dev/null @@ -1,524 +0,0 @@ -const { expect } = require("chai"); -const { utils, BigNumber } = require("ethers"); - -const { units, usdsUnits, usdtUnits } = require("../helpers"); -const { - createFixtureLoader, - oethCollateralSwapFixture, - ousdCollateralSwapFixture, - oeth1InchSwapperFixture, -} = require("../_fixture"); -const { - SWAP_SELECTOR, - UNISWAP_SELECTOR, - UNISWAPV3_SELECTOR, -} = require("../../utils/1Inch"); -const { impersonateAndFund } = require("../../utils/signers"); - -const log = require("../../utils/logger")("test:oeth:swapper"); - -describe("1Inch Swapper", () => { - describe("No OETH Collateral Swaps", () => { - let fixture; - const loadFixture = createFixtureLoader(oethCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should revert stETH to WETH swap", async () => { - const { weth, stETH, oethVault, strategist } = fixture; - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - // Call swap method - const tx = oethVault - .connect(strategist) - .swapCollateral(stETH.address, weth.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Collateral swap not supported"); - }); - - it("Should revert stETH to WETH swap", async () => { - const { stETH, weth, oethVault, strategist } = fixture; - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - // Call swap method - const tx = oethVault - .connect(strategist) - .swapCollateral(stETH.address, weth.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Collateral swap not supported"); - }); - }); - describe("OUSD Collateral Swaps", () => { - let fixture; - const loadFixture = createFixtureLoader(ousdCollateralSwapFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should allow Governor to set slippage for assets", async () => { - const { usds, governor, vault } = fixture; - - const tx = vault.connect(governor).setOracleSlippage(usds.address, 123); - await expect(tx) - .to.emit(vault, "SwapSlippageChanged") - .withArgs(usds.address, 123); - }); - - it("Should not allow Governor to set slippage for unsupported assets", async () => { - const { governor, vault, weth } = fixture; - - const tx = vault.connect(governor).setOracleSlippage(weth.address, 123); - await expect(tx).to.be.revertedWith("Asset not supported"); - }); - - it("Should not allow anyone else to set slippage for assets", async () => { - const { usds, strategist, josh, vault } = fixture; - - for (const user of [strategist, josh]) { - const tx = vault.connect(user).setOracleSlippage(usds.address, 123); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should not allow Governor to set slippage above 10%", async () => { - const { usds, governor, vault } = fixture; - - const tx = vault.connect(governor).setOracleSlippage(usds.address, 1100); - await expect(tx).to.be.revertedWith("Slippage too high"); - }); - - it("Should allow to change Swapper address", async () => { - const { governor, vault, weth } = fixture; - - // Pretend WETH is swapper address - const tx = vault.connect(governor).setSwapper(weth.address); - - await expect(tx).to.emit(vault, "SwapperChanged").withArgs(weth.address); - - expect(await vault.swapper()).to.equal(weth.address); - }); - - it("Should not allow anyone else to set swapper address", async () => { - const { strategist, josh, vault, weth } = fixture; - - for (const user of [strategist, josh]) { - const tx = vault.connect(user).setSwapper(weth.address); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should allow the governor to change allowed swap undervalue", async () => { - const { governor, vault } = fixture; - - const tx = vault.connect(governor).setSwapAllowedUndervalue(10); - - await expect(tx) - .to.emit(vault, "SwapAllowedUndervalueChanged") - .withArgs(10); - - expect(await vault.allowedSwapUndervalue()).to.equal(10); - }); - - it("Should not allow anyone else to set allowed swap undervalue", async () => { - const { strategist, josh, vault } = fixture; - - for (const user of [strategist, josh]) { - const tx = vault.connect(user).setSwapAllowedUndervalue(10); - await expect(tx).to.be.revertedWith("Caller is not the Governor"); - } - }); - - it("Should allow the governor to set allowed swap undervalue to 100%", async () => { - const { governor, vault } = fixture; - - const hundredPercent = 10000; - const tx = vault - .connect(governor) - .setSwapAllowedUndervalue(hundredPercent); - - await expect(tx) - .to.emit(vault, "SwapAllowedUndervalueChanged") - .withArgs(hundredPercent); - - expect(await vault.allowedSwapUndervalue()).to.equal(hundredPercent); - }); - - it("Should not allow setting undervalue percentage over 100%", async () => { - const { governor, vault } = fixture; - - const tx = vault.connect(governor).setSwapAllowedUndervalue(10001); - - await expect(tx).to.be.revertedWith("Invalid basis points"); - }); - - it("Should allow to swap tokens", async () => { - const { usds, usdc, usdt, vault, strategist } = fixture; - - for (const fromAsset of [usds, usdc, usdt]) { - for (const toAsset of [usds, usdc, usdt]) { - if (fromAsset.address === toAsset.address) continue; - const fromAmount = await units("20", fromAsset); - const toAmount = await units("21", toAsset); - log( - `swapping 20 ${await fromAsset.symbol()} to ${await toAsset.symbol()}` - ); - expect(await fromAsset.balanceOf(vault.address)).to.gte(fromAmount); - - // Call swap method - const tx = await vault - .connect(strategist) - .swapCollateral( - fromAsset.address, - toAsset.address, - fromAmount, - toAmount, - [] - ); - - expect(tx) - .to.emit(vault, "Swapped") - .withArgs(fromAsset.address, toAsset.address, fromAmount, toAmount); - } - } - }); - - it("Should revert swap if received less tokens than strategist desired", async () => { - const { usds, usdt, vault, strategist, mockSwapper } = fixture; - - // Mock to return lower than slippage next time - await mockSwapper.connect(strategist).setNextOutAmount(usdsUnits("18")); - - const fromAmount = usdtUnits("20"); - const toAmount = usdsUnits("20"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Strategist slippage limit"); - }); - - it("Should revert swap if received less tokens than Oracle slippage", async () => { - const { usds, usdt, vault, strategist } = fixture; - - const fromAmount = usdtUnits("20"); - const toAmount = usdsUnits("16"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Oracle slippage limit exceeded"); - }); - - it("Should revert swap if value is under supply", async () => { - const { usds, usdt, oeth, vault, governor, strategist, mockSwapper } = - fixture; - - // Mock to return lower than slippage next time - await mockSwapper - .connect(strategist) - .setNextOutAmount(utils.parseEther("180")); - // increase the allowed Oracle slippage per asset to 9.99% - await vault.connect(governor).setOracleSlippage(usds.address, 999); - await vault.connect(governor).setOracleSlippage(usdt.address, 999); - - const fromAmount = usdtUnits("200"); - const toAmount = usdsUnits("170"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("Allowed value < supply"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - }); - - it("Should allow swap if value is under supply by less than the allowed percentage", async () => { - const { usds, usdt, oeth, vault, governor, strategist, mockSwapper } = - fixture; - - // Mock to return lower than slippage next time - await mockSwapper.connect(strategist).setNextOutAmount(usdsUnits("19")); - // increase the allowed Oracle slippage per asset to 9.99% - await vault.connect(governor).setOracleSlippage(usds.address, 999); - await vault.connect(governor).setOracleSlippage(usdt.address, 999); - - const fromAmount = usdtUnits("20"); - const toAmount = usdsUnits("17"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - - // Call swap method - const tx = await vault - .connect(strategist) - .swapCollateral(usdt.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.emit(vault, "Swapped"); - - log(`total supply: ${await oeth.totalSupply()}`); - log(`total value : ${await vault.totalValue()}`); - }); - - it("Should revert if fromAsset is not supported", async () => { - const { usds, weth, vault, strategist } = fixture; - const fromAmount = utils.parseEther("100"); - const toAmount = usdsUnits("100"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(weth.address, usds.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("From asset is not supported"); - }); - - it("Should revert if toAsset is not supported", async () => { - const { weth, usds, vault, strategist } = fixture; - const fromAmount = usdsUnits("100"); - const toAmount = utils.parseEther("100"); - - // Call swap method - const tx = vault - .connect(strategist) - .swapCollateral(usds.address, weth.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith("To asset is not supported"); - }); - - it("Should swap if capital is paused", async () => { - const { usds, usdt, vault, strategist } = fixture; - const fromAmount = usdsUnits("100"); - const toAmount = usdtUnits("100"); - - // Fund Vault with some assets - const vaultSigner = await impersonateAndFund(vault.address); - await usds.connect(vaultSigner).mint(fromAmount); - - await vault.connect(strategist).pauseCapital(); - - // Call swap method - const tx = await vault - .connect(strategist) - .swapCollateral(usds.address, usdt.address, fromAmount, toAmount, []); - - expect(tx).to.emit(vault, "Swapped"); - }); - - it("Should revert if not called by Governor or Strategist", async () => { - const { usds, usdt, vault, josh } = fixture; - const fromAmount = usdsUnits("100"); - const toAmount = usdtUnits("100"); - - // Call swap method - const tx = vault - .connect(josh) - .swapCollateral(usds.address, usdt.address, fromAmount, toAmount, []); - - await expect(tx).to.be.revertedWith( - "Caller is not the Strategist or Governor" - ); - }); - }); - - describe.skip("1inch Swapper", () => { - let fixture; - const loadFixture = createFixtureLoader(oeth1InchSwapperFixture); - beforeEach(async () => { - fixture = await loadFixture(); - }); - - it("Should swap assets using 1inch router", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const deadAddr = "0x1111111111222222222233333333334444444444"; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "address", "bytes"], - [utils.arrayify(SWAP_SELECTOR), deadAddr, utils.arrayify("0xdead")] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, fromAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(mock1InchSwapRouter.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx) - .to.emit(mock1InchSwapRouter, "MockSwapDesc") - .withArgs( - weth.address, - frxETH.address, - deadAddr, - strategist.address, - fromAmount, - toAmount, - 4 - ); - - await expect(tx).to.emit( - mock1InchSwapRouter, - "MockSwap" - // ).withArgs( - // deadAddr, - // ['0', 'x'], - // utils.arrayify("0xdead") - ); - - const r = await (await tx).wait(); - expect(r.logs[3].data).to.equal( - "0x00000000000000000000000011111111112222222222333333333344444444440000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002dead000000000000000000000000000000000000000000000000000000000000" - ); - }); - - it("Should swap assets using Uniswap executor", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "uint256[]"], - [ - utils.arrayify(UNISWAP_SELECTOR), - [BigNumber.from("123"), BigNumber.from("456")], - ] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, fromAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx) - .to.emit(mock1InchSwapRouter, "MockUnoswapTo") - .withArgs(strategist.address, weth.address, fromAmount, toAmount, [ - BigNumber.from("123"), - BigNumber.from("456"), - ]); - }); - - it("Should swap assets using Uniswap V3 executor", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "uint256[]"], - [ - utils.arrayify(UNISWAPV3_SELECTOR), - [BigNumber.from("123"), BigNumber.from("456")], - ] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, fromAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx) - .to.emit(mock1InchSwapRouter, "MockUniswapV3SwapTo") - .withArgs(strategist.address, fromAmount, toAmount, [ - BigNumber.from("123"), - BigNumber.from("456"), - ]); - }); - - it("Should revert swap if fromAsset is insufficient ", async () => { - const { swapper1Inch, strategist, weth, frxETH } = fixture; - - const deadAddr = "0x1111111111222222222233333333334444444444"; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "address", "bytes"], - [utils.arrayify(SWAP_SELECTOR), deadAddr, utils.arrayify("0xdead")] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx).to.be.revertedWith( - "ERC20: transfer amount exceeds balance" - ); - }); - - it("Should revert swap if router allowance is insufficient ", async () => { - const { swapper1Inch, strategist, weth, frxETH, mock1InchSwapRouter } = - fixture; - - const deadAddr = "0x1111111111222222222233333333334444444444"; - - const data = utils.defaultAbiCoder.encode( - ["bytes4", "address", "bytes"], - [utils.arrayify(SWAP_SELECTOR), deadAddr, utils.arrayify("0xdead")] - ); - - const fromAmount = utils.parseEther("100"); - const toAmount = utils.parseEther("100"); - - await weth - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - await frxETH - .connect(strategist) - .mintTo(swapper1Inch.address, toAmount.mul(2)); - - // Reset allowance - await weth - .connect(await impersonateAndFund(swapper1Inch.address)) - .approve(mock1InchSwapRouter.address, 0); - - const tx = swapper1Inch - .connect(strategist) - .swap(weth.address, frxETH.address, fromAmount, toAmount, data); - - await expect(tx).to.be.revertedWith( - "ERC20: transfer amount exceeds allowance" - ); - }); - }); -}); diff --git a/contracts/test/vault/os-vault.sonic.js b/contracts/test/vault/os-vault.sonic.js index 37cd772451..3214c3c7b8 100644 --- a/contracts/test/vault/os-vault.sonic.js +++ b/contracts/test/vault/os-vault.sonic.js @@ -289,13 +289,6 @@ describe("Origin S Vault", function () { fixtureWithUser ); }); - - it("Should not support redeem", async () => { - const { oSonicVault, wS, nick } = fixture; - - const tx = oSonicVault.connect(nick).redeem(wS.address, 1); - await expect(tx).to.be.revertedWith("unsupported function"); - }); }); describe("Administer Sonic Staking Strategy", function () { diff --git a/contracts/test/vault/rebase.js b/contracts/test/vault/rebase.js index 67fe996539..c2aff4e085 100644 --- a/contracts/test/vault/rebase.js +++ b/contracts/test/vault/rebase.js @@ -3,11 +3,7 @@ const { expect } = require("chai"); const { loadDefaultFixture } = require("../_fixture"); const { ousdUnits, - usdsUnits, usdcUnits, - usdtUnits, - tusdUnits, - getOracleAddress, setOracleTokenPriceUsd, expectApproxSupply, } = require("../helpers"); @@ -169,24 +165,16 @@ describe("Vault rebase", () => { }); it("Should not allocate unallocated assets when no Strategy configured", async () => { - const { anna, governor, usds, usdc, usdt, tusd, vault } = fixture; + const { anna, governor, usdc, vault } = fixture; - await usds.connect(anna).transfer(vault.address, usdsUnits("100")); - await usdc.connect(anna).transfer(vault.address, usdcUnits("200")); - await usdt.connect(anna).transfer(vault.address, usdtUnits("300")); - await tusd.connect(anna).mintTo(vault.address, tusdUnits("400")); + await usdc.connect(anna).transfer(vault.address, usdcUnits("100")); expect(await vault.getStrategyCount()).to.equal(0); await vault.connect(governor).allocate(); - // All assets should still remain in Vault - - // Note defaultFixture sets up with 200 USDS already in the Strategy + // Note defaultFixture sets up with 200 USDC already in the Strategy // 200 + 100 = 300 - expect(await usds.balanceOf(vault.address)).to.equal(usdsUnits("300")); - expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits("200")); - expect(await usdt.balanceOf(vault.address)).to.equal(usdtUnits("300")); - expect(await tusd.balanceOf(vault.address)).to.equal(tusdUnits("400")); + expect(await usdc.balanceOf(vault.address)).to.equal(usdcUnits("300")); }); it("Should correctly handle a deposit of USDC (6 decimals)", async function () { @@ -199,24 +187,6 @@ describe("Vault rebase", () => { await vault.connect(anna).mint(usdc.address, usdcUnits("50"), 0); await expect(anna).has.a.balanceOf("50", ousd); }); - - it("Should allow priceProvider to be changed", async function () { - const { anna, governor, vault } = fixture; - - const oracle = await getOracleAddress(deployments); - await expect(await vault.priceProvider()).to.be.equal(oracle); - const annaAddress = await anna.getAddress(); - await vault.connect(governor).setPriceProvider(annaAddress); - await expect(await vault.priceProvider()).to.be.equal(annaAddress); - - // Only governor should be able to set it - await expect( - vault.connect(anna).setPriceProvider(oracle) - ).to.be.revertedWith("Caller is not the Governor"); - - await vault.connect(governor).setPriceProvider(oracle); - await expect(await vault.priceProvider()).to.be.equal(oracle); - }); }); describe("Vault yield accrual to OGN", async () => { @@ -230,7 +200,7 @@ describe("Vault rebase", () => { const { _yield, basis, expectedFee } = options; it(`should collect on rebase a ${expectedFee} fee from ${_yield} yield at ${basis}bp `, async function () { - const { matt, governor, ousd, usdt, vault, mockNonRebasing } = fixture; + const { matt, governor, ousd, usdc, vault, mockNonRebasing } = fixture; const trustee = mockNonRebasing; // Setup trustee on vault @@ -239,8 +209,8 @@ describe("Vault rebase", () => { await expect(trustee).has.a.balanceOf("0", ousd); // Create yield for the vault - await usdt.connect(matt).mint(usdcUnits(_yield)); - await usdt.connect(matt).transfer(vault.address, usdcUnits(_yield)); + await usdc.connect(matt).mint(usdcUnits(_yield)); + await usdc.connect(matt).transfer(vault.address, usdcUnits(_yield)); // Do rebase const supplyBefore = await ousd.totalSupply(); await vault.rebase(); diff --git a/contracts/test/vault/redeem.js b/contracts/test/vault/redeem.js index 5715cc9f51..299fd1b6ac 100644 --- a/contracts/test/vault/redeem.js +++ b/contracts/test/vault/redeem.js @@ -1,19 +1,11 @@ const { expect } = require("chai"); -const { BigNumber } = require("ethers"); - const { loadDefaultFixture } = require("../_fixture"); -const { - ousdUnits, - usdsUnits, - usdcUnits, - usdtUnits, - setOracleTokenPriceUsd, - isFork, - expectApproxSupply, -} = require("../helpers"); - -describe("Vault Redeem", function () { +const { ousdUnits, usdcUnits, isFork, advanceTime } = require("../helpers"); +const { impersonateAndFund } = require("../../utils/signers"); +const { deployWithConfirmation } = require("../../utils/deploy"); + +describe("OUSD Vault Withdrawals", function () { if (isFork) { this.timeout(0); } @@ -23,420 +15,1718 @@ describe("Vault Redeem", function () { fixture = await loadDefaultFixture(); }); - it("Should allow a redeem", async () => { - const { ousd, vault, usdc, anna, usds } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - await expect(anna).has.a.balanceOf("1000.00", usds); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - await vault.connect(anna).redeem(ousdUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("0.00", ousd); - // Redeem outputs will be 50/250 * 50 USDC and 200/250 * 50 USDS from fixture - await expect(anna).has.a.balanceOf("960.00", usdc); - await expect(anna).has.a.balanceOf("1040.00", usds); - expect(await ousd.totalSupply()).to.eq(ousdUnits("200.0")); - }); + const snapData = async (fixture) => { + const { ousd, vault, usdc, user } = fixture; + + const ousdTotalSupply = await ousd.totalSupply(); + const ousdTotalValue = await vault.totalValue(); + const vaultCheckBalance = await vault.checkBalance(usdc.address); + const userOusd = await ousd.balanceOf(user.address); + const userUsdc = await usdc.balanceOf(user.address); + const vaultUsdc = await usdc.balanceOf(vault.address); + const queue = await vault.withdrawalQueueMetadata(); + + return { + ousdTotalSupply, + ousdTotalValue, + vaultCheckBalance, + userOusd, + userUsdc, + vaultUsdc, + queue, + }; + }; + + const assertChangedData = async (dataBefore, delta, fixture) => { + const { ousd, vault, usdc, user } = fixture; - it("Should allow a redeem over the rebase threshold", async () => { - const { ousd, vault, usdc, anna, matt, usds } = fixture; + expect(await ousd.totalSupply(), "OUSD Total Supply").to.equal( + dataBefore.ousdTotalSupply.add(delta.ousdTotalSupply) + ); + expect(await vault.totalValue(), "Vault Total Value").to.equal( + dataBefore.ousdTotalValue.add(delta.ousdTotalValue) + ); + expect( + await vault.checkBalance(usdc.address), + "Vault Check Balance of USDC" + ).to.equal(dataBefore.vaultCheckBalance.add(delta.vaultCheckBalance)); + expect(await ousd.balanceOf(user.address), "user's OUSD balance").to.equal( + dataBefore.userOusd.add(delta.userOusd) + ); + expect(await usdc.balanceOf(user.address), "user's USDC balance").to.equal( + dataBefore.userUsdc.add(delta.userUsdc) + ); + expect(await usdc.balanceOf(vault.address), "Vault USDC balance").to.equal( + dataBefore.vaultUsdc.add(delta.vaultUsdc) + ); - await expect(anna).has.a.balanceOf("1000.00", usdc); - await expect(anna).has.a.balanceOf("1000.00", usds); + const queueAfter = await vault.withdrawalQueueMetadata(); + expect(queueAfter.queued, "Queued").to.equal( + dataBefore.queue.queued.add(delta.queued) + ); + expect(queueAfter.claimable, "Claimable").to.equal( + dataBefore.queue.claimable.add(delta.claimable) + ); + expect(queueAfter.claimed, "Claimed").to.equal( + dataBefore.queue.claimed.add(delta.claimed) + ); + expect(queueAfter.nextWithdrawalIndex, "nextWithdrawalIndex").to.equal( + dataBefore.queue.nextWithdrawalIndex.add(delta.nextWithdrawalIndex) + ); + }; + + describe("Withdrawal Queue", function () { + const delayPeriod = 10 * 60; // 10 minutes + beforeEach(async () => { + const { vault, josh, matt } = fixture; + + // In the fixture Matt and Josh mint 100 OUSD + // We should redeem that first to have only the 60 OUSD from USDC minting + await vault.connect(josh).requestWithdrawal(ousdUnits("100")); + await vault.connect(matt).requestWithdrawal(ousdUnits("100")); + + await advanceTime(delayPeriod); // 10 minutes + + await vault.connect(josh).claimWithdrawal(0); + await vault.connect(matt).claimWithdrawal(1); + }); + describe("with all 60 USDC in the vault", () => { + beforeEach(async () => { + const { vault, usdc, daniel, josh, matt } = fixture; + + // Fund three users with USDC + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("30")); + + // Approve vault to spend USDC + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("30")); + + // Mint some OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + + // Set max supply diff to 3% to allow withdrawals + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.03")); + }); + const firstRequestAmountOUSD = ousdUnits("5"); + const firstRequestAmountUSDC = usdcUnits("5"); + const secondRequestAmountOUSD = ousdUnits("18"); + const secondRequestAmountUSDC = usdcUnits("18"); + + // Positive Test + it("Should request first withdrawal by Daniel", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault + .connect(daniel) + .requestWithdrawal(firstRequestAmountOUSD); + + const queuedAmount = usdcUnits("205"); // 100 + 100 + 5 + + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(daniel.address, 2, firstRequestAmountOUSD, queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: firstRequestAmountOUSD.mul(-1), + ousdTotalValue: firstRequestAmountOUSD.mul(-1), + vaultCheckBalance: firstRequestAmountUSDC.mul(-1), + userOusd: firstRequestAmountOUSD.mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: firstRequestAmountUSDC, + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + it("Should request withdrawal of zero amount", async () => { + const { vault, josh } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + await vault.connect(josh).requestWithdrawal(firstRequestAmountOUSD); + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(josh).requestWithdrawal(0); + + const queuedAmount = usdcUnits("205"); // 100 + 100 + 5 + + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(josh.address, 3, 0, queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: 0, + queued: 0, + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + it("Should request first and second withdrawals with no USDC in the Vault", async () => { + const { vault, governor, josh, matt, usdc } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + + const mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + + // Deposit all 10 + 20 + 30 = 60 USDC to strategy + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("60")] + ); - await expect(anna).has.a.balanceOf("0.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + const dataBefore = await snapData(fixtureWithUser); + + await vault.connect(josh).requestWithdrawal(firstRequestAmountOUSD); + const tx = await vault + .connect(matt) + .requestWithdrawal(secondRequestAmountOUSD); + + const queuedAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 3, secondRequestAmountOUSD, queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: firstRequestAmountOUSD + .add(secondRequestAmountOUSD) + .mul(-1), + ousdTotalValue: firstRequestAmountOUSD + .add(secondRequestAmountOUSD) + .mul(-1), + vaultCheckBalance: firstRequestAmountUSDC + .add(secondRequestAmountUSDC) + .mul(-1), + userOusd: firstRequestAmountOUSD.mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 2, + }, + fixtureWithUser + ); + }); + it("Should request second withdrawal by matt", async () => { + const { vault, daniel, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault + .connect(matt) + .requestWithdrawal(secondRequestAmountOUSD); + + const queuedAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 3, secondRequestAmountOUSD, queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: secondRequestAmountOUSD.mul(-1), + ousdTotalValue: secondRequestAmountOUSD.mul(-1), + vaultCheckBalance: secondRequestAmountUSDC.mul(-1), + userOusd: secondRequestAmountOUSD.mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: secondRequestAmountUSDC, + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + it("Should add claimable liquidity to the withdrawal queue", async () => { + const { vault, daniel, josh } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(josh).requestWithdrawal(secondRequestAmountOUSD); + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(josh).addWithdrawalQueueLiquidity(); + + const claimableAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalClaimable") + .withArgs( + claimableAmount, + firstRequestAmountUSDC.add(secondRequestAmountUSDC) + ); - // Anna mints OUSD with USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("1000.00")); - await vault.connect(anna).mint(usdc.address, usdcUnits("1000.00"), 0); - await expect(anna).has.a.balanceOf("1000.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: 0, + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim second request with enough liquidity", async () => { + const { vault, daniel, josh } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(josh).requestWithdrawal(secondRequestAmountOUSD); + const requestId = 3; // ids start at 0 so the fourth request is at index 3. Two in set setup and two here. + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = await vault.connect(josh).claimWithdrawal(requestId); + + const claimableAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(josh.address, requestId, secondRequestAmountOUSD); + await expect(tx) + .to.emit(vault, "WithdrawalClaimable") + .withArgs( + claimableAmount, + firstRequestAmountUSDC.add(secondRequestAmountUSDC) + ); - // Anna mints OUSD with USDS - await usds.connect(anna).approve(vault.address, usdsUnits("1000.00")); - await vault.connect(anna).mint(usds.address, usdsUnits("1000.00"), 0); - await expect(anna).has.a.balanceOf("2000.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: secondRequestAmountUSDC, + vaultUsdc: secondRequestAmountUSDC.mul(-1), + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: secondRequestAmountUSDC, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim multiple requests with enough liquidity", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + await vault.connect(matt).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(matt).requestWithdrawal(secondRequestAmountOUSD); + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = await vault.connect(matt).claimWithdrawals([2, 3]); + + const claimableAmount = usdcUnits("223"); // 100 + 100 + 5 + 18 + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 2, firstRequestAmountOUSD); + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 3, secondRequestAmountOUSD); + await expect(tx) + .to.emit(vault, "WithdrawalClaimable") + .withArgs( + claimableAmount, + firstRequestAmountUSDC.add(secondRequestAmountUSDC) + ); - // Rebase should do nothing - await vault.rebase(); - await expect(anna).has.a.balanceOf("2000.00", ousd); - await expect(matt).has.a.balanceOf("100.00", ousd); + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + vaultUsdc: firstRequestAmountUSDC + .add(secondRequestAmountUSDC) + .mul(-1), + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim single big request as a whale", async () => { + const { vault, ousd, matt } = fixture; + + const ousdBalanceBefore = await ousd.balanceOf(matt.address); + const totalValueBefore = await vault.totalValue(); + + await vault.connect(matt).requestWithdrawal(ousdUnits("30")); + + const ousdBalanceAfter = await ousd.balanceOf(matt.address); + const totalValueAfter = await vault.totalValue(); + await expect(ousdBalanceBefore).to.equal(ousdUnits("30")); + await expect(ousdBalanceAfter).to.equal(ousdUnits("0")); + await expect(totalValueBefore.sub(totalValueAfter)).to.equal( + ousdUnits("30") + ); + + const ousdTotalSupply = await ousd.totalSupply(); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + const tx = await vault.connect(matt).claimWithdrawal(2); // Claim withdrawal for 50% of the supply + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 2, ousdUnits("30")); + + await expect(ousdTotalSupply).to.equal(await ousd.totalSupply()); + await expect(totalValueAfter).to.equal(await vault.totalValue()); + }); + + // Negative tests + it("Fail to claim request because of not enough time passed", async () => { + const { vault, daniel } = fixture; + + // Daniel requests 5 OUSD to be withdrawn + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + const requestId = 2; + + // Daniel claimWithdraw request in the same block as the request + const tx = vault.connect(daniel).claimWithdrawal(requestId); + + await expect(tx).to.revertedWith("Claim delay not met"); + }); + it("Fail to request withdrawal because of solvency check too high", async () => { + const { vault, daniel, usdc } = fixture; + + await usdc.mintTo(daniel.address, ousdUnits("10")); + await usdc.connect(daniel).transfer(vault.address, ousdUnits("10")); + + const tx = vault + .connect(daniel) + .requestWithdrawal(firstRequestAmountOUSD); + + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); + it("Fail to claim request because of solvency check too high", async () => { + const { vault, daniel, usdc } = fixture; + + // Request withdrawal of 5 OUSD + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + + // Transfer 10 USDC to the vault + await usdc.mintTo(daniel.address, ousdUnits("10")); + await usdc.connect(daniel).transfer(vault.address, ousdUnits("10")); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Claim the withdrawal + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); + it("Fail multiple claim requests because of solvency check too high", async () => { + const { vault, matt, usdc } = fixture; + + // Request withdrawal of 5 OUSD + await vault.connect(matt).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(matt).requestWithdrawal(secondRequestAmountOUSD); + + // Transfer 10 USDC to the vault + await usdc.mintTo(matt.address, ousdUnits("10")); + await usdc.connect(matt).transfer(vault.address, ousdUnits("10")); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Claim the withdrawal + const tx = vault.connect(matt).claimWithdrawals([2, 3]); + + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); + it("Fail request withdrawal because of solvency check too low", async () => { + const { vault, daniel, usdc } = fixture; + + // Simulate a loss of funds from the vault + await usdc + .connect(await impersonateAndFund(vault.address)) + .transfer(daniel.address, usdcUnits("10")); + + const tx = vault + .connect(daniel) + .requestWithdrawal(firstRequestAmountOUSD); - // Anna redeems over the rebase threshold - await vault.connect(anna).redeem(ousdUnits("1500.0"), 0); - await expect(anna).has.a.approxBalanceOf("500.00", ousd); - await expect(matt).has.a.approxBalanceOf("100.00", ousd); + await expect(tx).to.revertedWith("Backing supply liquidity error"); + }); - // Redeem outputs will be 1000/2200 * 1500 USDC and 1200/2200 * 1500 USDS from fixture - await expect(anna).has.an.approxBalanceOf("681.8181", usdc); - await expect(anna).has.a.approxBalanceOf("818.1818", usds); + describe("when deposit 15 USDC to a strategy, leaving 60 - 15 = 45 USDC in the vault; request withdrawal of 5 + 18 = 23 OUSD, leaving 45 - 23 = 22 USDC unallocated", () => { + let mockStrategy; + beforeEach(async () => { + const { vault, usdc, governor, daniel, josh } = fixture; - await expectApproxSupply(ousd, ousdUnits("700.0")); - }); + const dMockStrategy = await deployWithConfirmation("MockStrategy"); + mockStrategy = await ethers.getContractAt( + "MockStrategy", + dMockStrategy.address + ); + await mockStrategy.setWithdrawAll(usdc.address, vault.address); + await vault.connect(governor).approveStrategy(mockStrategy.address); - it("Changing an asset price affects a redeem", async () => { - const { ousd, vault, usds, matt } = fixture; + // Deposit 15 USDC of 10 + 20 + 30 = 60 USDC to strategy + // This leave 60 - 15 = 45 USDC in the vault + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("15")] + ); + // Request withdrawal of 5 + 18 = 23 OUSD + // This leave 45 - 23 = 22 USDC unallocated to the withdrawal queue + await vault.connect(daniel).requestWithdrawal(firstRequestAmountOUSD); + await vault.connect(josh).requestWithdrawal(secondRequestAmountOUSD); + }); + it("Fail to deposit allocated USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // 23 USDC to deposit > the 22 USDC available so it should revert + const depositAmount = usdcUnits("23"); + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [depositAmount] + ); + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Fail to deposit allocated USDC during allocate", async () => { + const { vault, governor, usdc } = fixture; - await expectApproxSupply(ousd, ousdUnits("200")); - await expect(matt).has.a.balanceOf("100.00", ousd); - await expect(matt).has.a.balanceOf("900.00", usds); + // Set mock strategy as default strategy + await vault + .connect(governor) + .setDefaultStrategy(mockStrategy.address); - await setOracleTokenPriceUsd("USDS", "1.25"); - await vault.rebase(); + // and buffer to 10% + await vault.connect(governor).setVaultBuffer(ousdUnits("0.1")); - await vault.connect(matt).redeem(ousdUnits("2.0"), 0); - await expectApproxSupply(ousd, ousdUnits("198")); - // Amount of USDS collected is affected by redeem oracles - await expect(matt).has.a.approxBalanceOf("901.60", usds); - }); + // USDC in strategy = 15 USDC + // USDC in the vault = 60 - 15 = 45 USDC + // Unallocated USDC in the vault = 45 - 23 = 22 USDC - it("Should allow redeems of non-standard tokens", async () => { - const { ousd, vault, anna, governor, oracleRouter, nonStandardToken } = - fixture; + await vault.connect(governor).allocate(); - await oracleRouter.cacheDecimals(nonStandardToken.address); - await vault.connect(governor).supportAsset(nonStandardToken.address, 0); + expect(await usdc.balanceOf(mockStrategy.address)).to.approxEqual( + // 60 - 23 = 37 Unreserved USDC + // 90% of 37 = 33.3 USDC for allocation + usdcUnits("33.3"), + "Strategy has the reserved USDC" + ); - await setOracleTokenPriceUsd("NonStandardToken", "1.00"); + expect(await usdc.balanceOf(vault.address)).to.approxEqual( + // 10% of 37 = 3.7 USDC for Vault buffer + // + 23 reserved USDC + usdcUnits("23").add(usdcUnits("3.7")), + "Vault doesn't have enough USDC" + ); + }); + it("Should deposit unallocated USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; - await expect(anna).has.a.balanceOf("1000.00", nonStandardToken); + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + const depositAmount = usdcUnits("22"); + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [depositAmount] + ); + }); + it("Should claim first request with enough liquidity", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const requestId = 2; + const tx = await vault.connect(daniel).claimWithdrawal(requestId); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, requestId, firstRequestAmountOUSD); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: firstRequestAmountUSDC, + vaultUsdc: firstRequestAmountUSDC.mul(-1), + queued: 0, + claimable: firstRequestAmountUSDC.add(secondRequestAmountUSDC), + claimed: firstRequestAmountUSDC, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should claim a new request with enough USDC liquidity", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Matt request all unallocated USDC to be withdrawn + const requestAmount = ousdUnits("22"); + await vault.connect(matt).requestWithdrawal(requestAmount); + + const dataBefore = await snapData(fixtureWithUser); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const requestId = 4; + const tx = await vault.connect(matt).claimWithdrawal(requestId); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, requestId, requestAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: requestAmount.div(1e12), // USDC has 6 decimals + vaultUsdc: requestAmount.mul(-1).div(1e12), + queued: 0, + claimable: requestAmount.div(1e12), + claimed: requestAmount.div(1e12), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to claim a new request with NOT enough USDC liquidity", async () => { + const { vault, matt } = fixture; - // Mint 100 OUSD for 100 tokens - await nonStandardToken - .connect(anna) - .approve(vault.address, usdtUnits("100.0")); - await vault - .connect(anna) - .mint(nonStandardToken.address, usdtUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); + // Matt request 23 OUSD to be withdrawn when only 22 USDC is unallocated to existing requests + const requestAmount = ousdUnits("23"); + await vault.connect(matt).requestWithdrawal(requestAmount); - // Redeem 100 tokens for 100 OUSD - await vault.connect(anna).redeem(ousdUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("0.00", ousd); - // 66.66 would have come back as USDS because there is 100 NST and 200 USDS - await expect(anna).has.an.approxBalanceOf("933.33", nonStandardToken); - }); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. - it("Should have a default redeem fee of 0", async () => { - const { vault } = fixture; + const tx = vault.connect(matt).claimWithdrawal(4); + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Should claim a new request after withdraw from strategy adds enough liquidity", async () => { + const { vault, daniel, matt, strategist, usdc } = fixture; - await expect(await vault.redeemFeeBps()).to.equal("0"); - }); + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); - it("Should charge a redeem fee if redeem fee set", async () => { - const { ousd, vault, usdc, anna, governor } = fixture; - - // 1000 basis points = 10% - await vault.connect(governor).setRedeemFeeBps(1000); - await expect(anna).has.a.balanceOf("1000.00", usdc); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - await vault.connect(anna).redeem(ousdUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("0.00", ousd); - // 45 after redeem fee - // USDC is 50/250 of total assets, so balance should be 950 + 50/250 * 45 = 959 - await expect(anna).has.a.balanceOf("959.00", usdc); - }); + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); - it("Should revert redeem if balance is insufficient", async () => { - const { ousd, vault, usdc, anna } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); - // Mint some OUSD tokens - await expect(anna).has.a.balanceOf("1000.00", usdc); - await usdc.connect(anna).approve(vault.address, usdcUnits("50.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + const withdrawAmount = usdcUnits("8"); + await vault + .connect(strategist) + .withdrawFromStrategy( + mockStrategy.address, + [usdc.address], + [withdrawAmount] + ); - // Try to withdraw more than balance - await expect( - vault.connect(anna).redeem(ousdUnits("100.0"), 0) - ).to.be.revertedWith("Transfer amount exceeds balance"); - }); + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: withdrawAmount, + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); - it("Should only allow Governor to set a redeem fee", async () => { - const { vault, anna } = fixture; + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. - await expect(vault.connect(anna).setRedeemFeeBps(100)).to.be.revertedWith( - "Caller is not the Governor" - ); - }); + await vault.connect(matt).claimWithdrawal(4); + }); + it("Should claim a new request after withdrawAllFromStrategy adds enough liquidity", async () => { + const { vault, daniel, matt, strategist, usdc } = fixture; - it("Should redeem entire OUSD balance", async () => { - const { ousd, vault, usdc, usds, anna } = fixture; + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); - await expect(anna).has.a.balanceOf("1000.00", usdc); + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); - // Mint 100 OUSD tokens using USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); + const strategyBalanceBefore = await usdc.balanceOf( + mockStrategy.address + ); - // Mint 150 OUSD tokens using USDS - await usds.connect(anna).approve(vault.address, usdsUnits("150.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("150.0"), 0); - await expect(anna).has.a.balanceOf("250.00", ousd); + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + await vault + .connect(strategist) + .withdrawAllFromStrategy(mockStrategy.address); + + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: strategyBalanceBefore, + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); - // Withdraw all - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. - // 100 USDC and 350 USDS in contract - // (1000-100) + 100/450 * 250 USDC - // (1000-150) + 350/450 * 250 USDS - await expect(anna).has.an.approxBalanceOf("955.55", usdc); - await expect(anna).has.an.approxBalanceOf("1044.44", usds); - }); + await vault.connect(matt).claimWithdrawal(4); + }); + it("Should claim a new request after withdrawAll from strategies adds enough liquidity", async () => { + const { vault, daniel, matt, strategist, usdc } = fixture; - it("Should redeem entire OUSD balance, with a higher oracle price", async () => { - const { ousd, vault, usdc, usds, anna, governor } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - - // Mint 100 OUSD tokens using USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); - - // Mint 150 OUSD tokens using USDS - await usds.connect(anna).approve(vault.address, usdsUnits("150.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("150.0"), 0); - await expect(anna).has.a.balanceOf("250.00", ousd); - - await setOracleTokenPriceUsd("USDC", "1.30"); - await setOracleTokenPriceUsd("USDS", "1.20"); - await vault.connect(governor).rebase(); - - // Anna's balance does not change with the rebase - await expect(anna).has.an.approxBalanceOf("250.00", ousd); - - // Withdraw all - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); - - // OUSD to Withdraw 250 - // Total Vault Coins 450 - // USDC Percentage 100 / 450 = 0.222222222222222 - // USDT Percentage 350 / 450 = 0.777777777777778 - // USDC Value Percentage 0.222222222222222 * 1.3 = 0.288888888888889 - // USDT Value Percentage 0.777777777777778 * 1.2 = 0.933333333333333 - // Output to Dollar Ratio 1.22222222222222 - // USDC Output 250 * 0.222222222222222 / 1.22222222222222 = 45.4545454545454 - // USDT Output 250 * 0.777777777777778 / 1.22222222222222 = 159.090909090909 - // Expected USDC 900 + 45.4545454545454 = 945.454545454545 - // Expected USDT 850 + 159.090909090909 = 1009.09090909091 - await expect(anna).has.an.approxBalanceOf( - "945.4545", - usdc, - "USDC has wrong balance" - ); - await expect(anna).has.an.approxBalanceOf( - "1009.09", - usds, - "USDS has wrong balance" - ); - }); + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); - it("Should redeem entire OUSD balance, with a lower oracle price", async () => { - const { ousd, vault, usdc, usds, anna, governor } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - - // Mint 100 OUSD tokens using USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("100.0"), 0); - await expect(anna).has.a.balanceOf("100.00", ousd); - - // Mint 150 OUSD tokens using USDS - await usds.connect(anna).approve(vault.address, usdsUnits("150.0")); - await vault.connect(anna).mint(usds.address, usdsUnits("150.0"), 0); - await expect(anna).has.a.balanceOf("250.00", ousd); - - await setOracleTokenPriceUsd("USDC", "0.90"); - await setOracleTokenPriceUsd("USDS", "0.80"); - await vault.connect(governor).rebase(); - - // Anna's share of OUSD is unaffected - await expect(anna).has.an.approxBalanceOf("250.00", ousd); - - // Withdraw all - await ousd.connect(anna).approve(vault.address, ousdUnits("500")); - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); - - // OUSD to Withdraw 250 - // Total Vault Coins 450 - // USDC Percentage 100 / 450 = 0.2222 - // USDS Percentage 350 / 450 = 0.7778 - // USDC Value Percentage 0.2222 * 1 = 0.2222 - // USDS Value Percentage 0.7778 * 1 = 0.7778 - // Output to Dollar Ratio 1.0000 - // USDC Output 250 * 0.2222 / 1.0000 = 55.5556 - // USDS Output 250 * 0.7778 / 1.0000 = 194.4444 - // Expected USDC 900 + 55.5556 = 955.5556 - // Expected USDS 850 + 194.4444 = 1044.4444 - await expect(anna).has.an.approxBalanceOf( - "955.5556", - usdc, - "USDC has wrong balance" - ); - await expect(anna).has.an.approxBalanceOf( - "1044.44", - usds, - "USDS has wrong balance" - ); - }); + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); - it("Should have correct balances on consecutive mint and redeem", async () => { - const { ousd, vault, usdc, usds, anna, matt, josh } = fixture; - - const usersWithBalances = [ - [anna, 0], - [matt, 100], - [josh, 100], - ]; - - const assetsWithUnits = [ - [usds, usdsUnits], - [usdc, usdcUnits], - ]; - - for (const [user, startBalance] of usersWithBalances) { - for (const [asset, units] of assetsWithUnits) { - for (const amount of [5.09, 10.32, 20.99, 100.01]) { - await asset - .connect(user) - .approve(vault.address, await units(amount.toString())); - await vault - .connect(user) - .mint(asset.address, await units(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - (startBalance + amount).toString(), - ousd + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); + const strategyBalanceBefore = await usdc.balanceOf( + mockStrategy.address ); - await vault.connect(user).redeem(ousdUnits(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - startBalance.toString(), - ousd + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + await vault.connect(strategist).withdrawAllFromStrategies(); + + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: 0, + vaultUsdc: strategyBalanceBefore, + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser ); - } - } - } - }); - it("Should have correct balances on consecutive mint and redeem with varying oracle prices", async () => { - const { ousd, vault, usdt, usdc, matt, josh } = fixture; + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + await vault.connect(matt).claimWithdrawal(4); + }); + it("Fail to claim a new request after mint with NOT enough liquidity", async () => { + const { vault, daniel, matt, usdc } = fixture; + + // Matt requests all 30 OUSD to be withdrawn which is not enough liquidity + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 6 USDC so the unallocated USDC is 22 + 6 = 28 USDC + await usdc.mintTo(daniel.address, ousdUnits("6").div(1e12)); + await usdc + .connect(daniel) + .approve(vault.address, ousdUnits("6").div(1e12)); + await vault.connect(daniel).mint(usdc.address, usdcUnits("6"), 0); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = vault.connect(matt).claimWithdrawal(4); + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Should claim a new request after mint adds enough liquidity", async () => { + const { vault, daniel, matt, usdc } = fixture; + + // Set the claimable amount to the queued amount + await vault.addWithdrawalQueueLiquidity(); + + // Matt requests all 30 OUSD to be withdrawn which is currently 8 USDC short + const requestAmount = ousdUnits("30"); + await vault.connect(matt).requestWithdrawal(requestAmount); + + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBeforeMint = await snapData(fixtureWithUser); + + // USDC in the vault = 60 - 15 = 45 USDC + // unallocated USDC in the Vault = 45 - 23 = 22 USDC + // Add another 8 USDC so the unallocated USDC is 22 + 8 = 30 USDC + const mintAmount = ousdUnits("8"); + await usdc + .connect(daniel) + .approve(vault.address, mintAmount.div(1e12)); + await vault + .connect(daniel) + .mint(usdc.address, mintAmount.div(1e12), 0); + + await assertChangedData( + dataBeforeMint, + { + ousdTotalSupply: mintAmount, + ousdTotalValue: mintAmount, + vaultCheckBalance: mintAmount.div(1e12), + userOusd: mintAmount, + userUsdc: mintAmount.mul(-1).div(1e12), + vaultUsdc: mintAmount.div(1e12), + queued: 0, + claimable: requestAmount.div(1e12), + claimed: 0, + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); - const users = [matt, josh]; - const assetsWithUnits = [ - [usdt, usdtUnits], - [usdc, usdcUnits], - ]; - const prices = [0.998, 1.02, 1.09]; - const amounts = [5.09, 10.32, 20.99, 100.01]; + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + await vault.connect(matt).claimWithdrawal(4); + }); + }); + + describe("Fail when", () => { + it("request doesn't have enough OUSD", async () => { + const { vault, josh } = fixture; + const fixtureWithUser = { ...fixture, user: josh }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = vault + .connect(josh) + .requestWithdrawal(dataBefore.userOusd.add(1)); + + await expect(tx).to.revertedWith("Transfer amount exceeds balance"); + }); + it("capital is paused", async () => { + const { vault, governor, josh } = fixture; + + await vault.connect(governor).pauseCapital(); + + const tx = vault + .connect(josh) + .requestWithdrawal(firstRequestAmountOUSD); + + await expect(tx).to.be.revertedWith("Capital paused"); + }); + }); + }); + describe("with 1% vault buffer, 30 USDC in the queue, 15 USDC in the vault, 85 USDC in the strategy, 5 USDC already claimed", () => { + let mockStrategy; + beforeEach(async () => { + const { governor, vault, usdc, daniel, domen, josh, matt } = fixture; + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("15")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("30")); + await usdc.mintTo(domen.address, usdcUnits("40")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("15")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("30")); + await usdc.connect(domen).approve(vault.address, usdcUnits("40")); + + // Mint 105 OUSD to four users + await vault.connect(daniel).mint(usdc.address, usdcUnits("15"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + await vault.connect(domen).mint(usdc.address, usdcUnits("40"), "0"); + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.03")); + + // Request and claim 2 + 3 = 5 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("2")); + await vault.connect(josh).requestWithdrawal(ousdUnits("3")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + await vault.connect(daniel).claimWithdrawal(2); + await vault.connect(josh).claimWithdrawal(3); + + // Deploy a mock strategy + mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + + // Deposit 85 USDC to strategy + await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("85")] + ); - const getUserOusdBalance = async (user) => { - const bn = await ousd.balanceOf(await user.getAddress()); - return parseFloat(bn.toString() / 1e12 / 1e6); - }; + // Set vault buffer to 1% + await vault.connect(governor).setVaultBuffer(ousdUnits("0.01")); + + // Have 4 + 12 + 16 = 32 USDC outstanding requests + // So a total supply of 100 - 32 = 68 OUSD + await vault.connect(daniel).requestWithdrawal(ousdUnits("4")); + await vault.connect(josh).requestWithdrawal(ousdUnits("12")); + await vault.connect(matt).requestWithdrawal(ousdUnits("16")); + + await vault.connect(josh).addWithdrawalQueueLiquidity(); + }); + describe("Fail to claim", () => { + it("a previously claimed withdrawal", async () => { + const { vault, daniel } = fixture; + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Already claimed"); + }); + it("the first withdrawal with wrong withdrawer", async () => { + const { vault, matt } = fixture; + + // Advance in time to ensure time delay between request and claim. + await advanceTime(delayPeriod); + + const tx = vault.connect(matt).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Not requester"); + }); + it("the first withdrawal request in the queue before 30 minutes", async () => { + const { vault, daniel } = fixture; + + const tx = vault.connect(daniel).claimWithdrawal(4); + + await expect(tx).to.be.revertedWith("Claim delay not met"); + }); + }); + describe("when waited 30 minutes", () => { + beforeEach(async () => { + // Advance in time to ensure time delay between request and claim. + await advanceTime(delayPeriod); + }); + it("Fail to claim the first withdrawal with wrong withdrawer", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).claimWithdrawal(4); + + await expect(tx).to.be.revertedWith("Not requester"); + }); + it("Should claim the first withdrawal request in the queue after 30 minutes", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const requestId = 4; + const tx = await vault.connect(daniel).claimWithdrawal(requestId); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, requestId, ousdUnits("4")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("4"), + vaultUsdc: usdcUnits("4").mul(-1), + queued: 0, + claimable: 0, + claimed: usdcUnits("4"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to claim the second withdrawal request in the queue after 30 minutes", async () => { + const { vault, josh } = fixture; + + const tx = vault.connect(josh).claimWithdrawal(5); + + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Fail to claim the last (3rd) withdrawal request in the queue", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).claimWithdrawal(6); + + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + }); + describe("when mint covers exactly outstanding requests (32 - 15 = 17 OUSD)", () => { + beforeEach(async () => { + const { vault, daniel, usdc } = fixture; + await usdc.mintTo(daniel.address, usdcUnits("17")); + await usdc.connect(daniel).approve(vault.address, usdcUnits("17")); + await vault.connect(daniel).mint(usdc.address, usdcUnits("17"), "0"); + + // Advance in time to ensure time delay between request and claim. + await advanceTime(delayPeriod); + }); + it("Should claim the 2nd and 3rd withdrawal requests in the queue", async () => { + const { vault, daniel, josh } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const tx1 = await vault.connect(daniel).claimWithdrawal(4); + + await expect(tx1) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, 4, ousdUnits("4")); + + const tx2 = await vault.connect(josh).claimWithdrawal(5); + + await expect(tx2) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(josh.address, 5, ousdUnits("12")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("4"), + vaultUsdc: usdcUnits("16").mul(-1), + queued: 0, + claimable: 0, + claimed: usdcUnits("16"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to deposit 1 USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("1")] + ); - for (const user of users) { - for (const [asset, units] of assetsWithUnits) { - for (const price of prices) { - await setOracleTokenPriceUsd(await asset.symbol(), price.toString()); - // Manually call rebase because not triggered by mint - await vault.rebase(); - // Rebase could have changed user balance - // as there could have been yield from different - // oracle prices on redeems during a previous loop. - let userBalance = await getUserOusdBalance(user); - for (const amount of amounts) { - const ousdToReceive = amount * Math.min(price, 1); - await expect(user).has.an.approxBalanceOf( - userBalance.toString(), - ousd + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Fail to allocate any USDC to the default strategy", async () => { + const { vault, domen } = fixture; + + const tx = await vault.connect(domen).allocate(); + + await expect(tx).to.not.emit(vault, "AssetAllocated"); + }); + }); + describe("when mint covers exactly outstanding requests and vault buffer (17 + 1 USDC)", () => { + beforeEach(async () => { + const { vault, daniel, usdc } = fixture; + await usdc.mintTo(daniel.address, usdcUnits("18")); + await usdc.connect(daniel).approve(vault.address, usdcUnits("18")); + await vault.connect(daniel).mint(usdc.address, usdcUnits("18"), "0"); + }); + it("Should deposit 1 USDC to a strategy which is the vault buffer", async () => { + const { vault, usdc, governor } = fixture; + + const tx = await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("1")] ); - await asset - .connect(user) - .approve(vault.address, await units(amount.toString())); - await vault - .connect(user) - .mint(asset.address, await units(amount.toString()), 0); - await expect(user).has.an.approxBalanceOf( - (userBalance + ousdToReceive).toString(), - ousd + + expect(tx) + .to.emit(usdc, "Transfer") + .withArgs(vault.address, mockStrategy.address, usdcUnits("1")); + }); + it("Fail to deposit 1.1 USDC to the default strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("1.1")] ); - await vault - .connect(user) - .redeem(ousdUnits(ousdToReceive.toString()), 0); - await expect(user).has.an.approxBalanceOf( - userBalance.toString(), - ousd + + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Fail to allocate any USDC to the default strategy", async () => { + const { vault, domen } = fixture; + + const tx = await vault.connect(domen).allocate(); + + await expect(tx).to.not.emit(vault, "AssetAllocated"); + }); + }); + describe("when mint more than covers outstanding requests and vault buffer (17 + 1 + 3 = 21 OUSD)", () => { + beforeEach(async () => { + const { vault, daniel, usdc } = fixture; + await usdc.mintTo(daniel.address, usdcUnits("21")); + await usdc.connect(daniel).approve(vault.address, usdcUnits("21")); + await vault.connect(daniel).mint(usdc.address, usdcUnits("21"), "0"); + }); + it("Should deposit 4 USDC to a strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = await vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("4")] ); - } - } - } - } - }); - it("Should correctly handle redeem without a rebase and then full redeem", async function () { - const { ousd, vault, usdc, anna } = fixture; - await expect(anna).has.a.balanceOf("0.00", ousd); - await usdc.connect(anna).mint(usdcUnits("3000.0")); - await usdc.connect(anna).approve(vault.address, usdcUnits("3000.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("3000.0"), 0); - await expect(anna).has.a.balanceOf("3000.00", ousd); - - //peturb the oracle a slight bit. - await setOracleTokenPriceUsd("USDC", "1.000001"); - //redeem without rebasing (not over threshold) - await vault.connect(anna).redeem(ousdUnits("200.00"), 0); - //redeem with rebasing (over threshold) - await vault.connect(anna).redeem(ousd.balanceOf(anna.address), 0); - - await expect(anna).has.a.balanceOf("0.00", ousd); - }); + expect(tx) + .to.emit(usdc, "Transfer") + .withArgs(vault.address, mockStrategy.address, usdcUnits("4")); + }); + it("Fail to deposit 5 USDC to the default strategy", async () => { + const { vault, usdc, governor } = fixture; + + const tx = vault + .connect(governor) + .depositToStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("5")] + ); - it("Should respect minimum unit amount argument in redeem", async () => { - const { ousd, vault, usdc, anna, usds } = fixture; - - await expect(anna).has.a.balanceOf("1000.00", usdc); - await expect(anna).has.a.balanceOf("1000.00", usds); - await usdc.connect(anna).approve(vault.address, usdcUnits("100.0")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect(anna).has.a.balanceOf("50.00", ousd); - await vault.connect(anna).redeem(ousdUnits("50.0"), ousdUnits("50")); - await vault.connect(anna).mint(usdc.address, usdcUnits("50.0"), 0); - await expect( - vault.connect(anna).redeem(ousdUnits("50.0"), ousdUnits("51")) - ).to.be.revertedWith("Redeem amount lower than minimum"); - }); + await expect(tx).to.be.revertedWith("Not enough assets available"); + }); + it("Should allocate 3 USDC to the default strategy", async () => { + const { vault, governor, domen, usdc } = fixture; + + await vault + .connect(governor) + .setDefaultStrategy(mockStrategy.address); + + const vaultBalance = await usdc.balanceOf(vault.address); + const stratBalance = await usdc.balanceOf(mockStrategy.address); + + const tx = await vault.connect(domen).allocate(); + + // total supply is 68 starting + 21 minted = 89 OUSD + // Vault buffer is 1% of 89 = 0.89 USDC + // USDC transfer amount = 4 USDC available in vault - 0.89 USDC buffer = 3.11 USDC + await expect(tx) + .to.emit(vault, "AssetAllocated") + .withArgs(usdc.address, mockStrategy.address, usdcUnits("3.11")); + + expect(await usdc.balanceOf(vault.address)).to.eq( + vaultBalance.sub(usdcUnits("3.11")) + ); + + expect(await usdc.balanceOf(mockStrategy.address)).to.eq( + stratBalance.add(usdcUnits("3.11")) + ); + }); + }); + }); + describe("with 40 USDC in the queue, 10 USDC in the vault, 30 USDC already claimed", () => { + beforeEach(async () => { + const { vault, usdc, daniel, josh, matt } = fixture; + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("10")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("10")); + + // Mint 60 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("10"), "0"); + + // Request and claim 10 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("10")); + await vault.connect(josh).requestWithdrawal(ousdUnits("20")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Claim 10 + 20 = 30 USDC from Vault + await vault.connect(daniel).claimWithdrawal(2); + await vault.connect(josh).claimWithdrawal(3); + }); + it("Should allow the last user to request the remaining 10 USDC", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).requestWithdrawal(ousdUnits("10")); + + const queuedAmount = usdcUnits("240"); // 110 + 100 + 10 + 20 + 10 + await expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 4, ousdUnits("10"), queuedAmount); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: ousdUnits("10").mul(-1), + ousdTotalValue: ousdUnits("10").mul(-1), + vaultCheckBalance: usdcUnits("10").mul(-1), + userOusd: ousdUnits("10").mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: usdcUnits("10").mul(1), + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + it("Should allow the last user to claim the request of 10 USDC", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + await vault.connect(matt).requestWithdrawal(ousdUnits("10")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).claimWithdrawal(4); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 4, ousdUnits("10")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("10"), + vaultUsdc: usdcUnits("10").mul(-1), + queued: 0, + claimable: usdcUnits("10"), + claimed: usdcUnits("10"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + + expect(await vault.totalValue()).to.equal(0); + }); + }); + describe("with 40 USDC in the queue, 100 USDC in the vault, 0 USDC in the strategy", () => { + beforeEach(async () => { + const { vault, usdc, daniel, josh, matt } = fixture; + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("70")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("70")); + + // Mint 100 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("70"), "0"); + + // Request 40 USDC from Vault + await vault.connect(matt).requestWithdrawal(ousdUnits("40")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + }); + it("Should allow user to claim the request of 40 USDC", async () => { + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).claimWithdrawal(2); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 2, ousdUnits("40")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("40"), + vaultUsdc: usdcUnits("40").mul(-1), + queued: 0, + claimable: usdcUnits("40"), + claimed: usdcUnits("40"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Should allow user to perform a new request and claim a smaller than the USDC available", async () => { + const { vault, josh } = fixture; + + await vault.connect(josh).requestWithdrawal(ousdUnits("20")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const tx = await vault.connect(josh).claimWithdrawal(3); + + await expect(tx).to.emit(vault, "WithdrawalClaimed"); + }); + it("Should allow user to perform a new request and claim exactly the USDC available", async () => { + const { vault, ousd, josh, matt, daniel } = fixture; + await vault.connect(matt).claimWithdrawal(2); + // All user give OUSD to another user + await ousd.connect(josh).transfer(matt.address, ousdUnits("20")); + await ousd.connect(daniel).transfer(matt.address, ousdUnits("10")); + + const fixtureWithUser = { ...fixture, user: matt }; + + // Matt request the remaining 60 OUSD to be withdrawn + await vault.connect(matt).requestWithdrawal(ousdUnits("60")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(matt).claimWithdrawal(3); + + await expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(matt.address, 3, ousdUnits("60")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("60"), + vaultUsdc: usdcUnits("60").mul(-1), + queued: 0, + claimable: usdcUnits("60"), + claimed: usdcUnits("60"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Shouldn't allow user to perform a new request and claim more than the USDC available", async () => { + const { vault, ousd, usdc, josh, matt, daniel, governor } = fixture; + await vault.connect(matt).claimWithdrawal(2); + // All user give OUSD to another user + await ousd.connect(josh).transfer(matt.address, ousdUnits("20")); + await ousd.connect(daniel).transfer(matt.address, ousdUnits("10")); + + // Matt request more than the remaining 60 OUSD to be withdrawn + await vault.connect(matt).requestWithdrawal(ousdUnits("60")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + await usdc + .connect(await impersonateAndFund(vault.address)) + .transfer(governor.address, usdcUnits("50")); // Vault loses 50 USDC + + const tx = vault.connect(matt).claimWithdrawal(3); + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + }); + describe("with 40 USDC in the queue, 15 USDC in the vault, 44 USDC in the strategy, vault insolvent by 5% => Slash 1 ether (1/20 = 5%), 19 USDC total value", () => { + beforeEach(async () => { + const { governor, vault, usdc, daniel, josh, matt, strategist } = + fixture; + // Deploy a mock strategy + const mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("10")); + await usdc.mintTo(josh.address, usdcUnits("20")); + await usdc.mintTo(matt.address, usdcUnits("30")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("10")); + await usdc.connect(josh).approve(vault.address, usdcUnits("20")); + await usdc.connect(matt).approve(vault.address, usdcUnits("30")); + + // Mint 60 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("10"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("30"), "0"); + + await vault.allocate(); + // Request and claim 10 + 20 + 10 = 40 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("10")); + await vault.connect(josh).requestWithdrawal(ousdUnits("20")); + await vault.connect(matt).requestWithdrawal(ousdUnits("10")); + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Simulate slash event of 1 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("1")); + + // Strategist sends 15 USDC to the vault + await vault + .connect(strategist) + .withdrawFromStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("15")] + ); + + await vault.connect(josh).addWithdrawalQueueLiquidity(); + }); + it("Should allow first user to claim the request of 10 USDC", async () => { + const { vault, daniel } = fixture; + const fixtureWithUser = { ...fixture, user: daniel }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = await vault.connect(daniel).claimWithdrawal(2); + + expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, 2, ousdUnits("10")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: 0, + ousdTotalValue: 0, + vaultCheckBalance: 0, + userOusd: 0, + userUsdc: usdcUnits("10"), + vaultUsdc: usdcUnits("10").mul(-1), + queued: 0, + claimable: 0, + claimed: usdcUnits("10"), + nextWithdrawalIndex: 0, + }, + fixtureWithUser + ); + }); + it("Fail to allow second user to claim the request of 20 USDC, due to liquidity", async () => { + const { vault, josh } = fixture; + + const tx = vault.connect(josh).claimWithdrawal(3); + + await expect(tx).to.be.revertedWith("Queue pending liquidity"); + }); + it("Should allow a user to create a new request with solvency check off", async () => { + // maxSupplyDiff is set to 0 so no insolvency check + const { vault, matt } = fixture; + const fixtureWithUser = { ...fixture, user: matt }; + const dataBefore = await snapData(fixtureWithUser); + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("10")); + + expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 5, ousdUnits("10"), ousdUnits("50")); + + await assertChangedData( + dataBefore, + { + ousdTotalSupply: ousdUnits("10").mul(-1), + ousdTotalValue: ousdUnits("10").mul(-1), + vaultCheckBalance: usdcUnits("10").mul(-1), + userOusd: ousdUnits("10").mul(-1), + userUsdc: 0, + vaultUsdc: 0, + queued: usdcUnits("10").mul(1), + claimable: 0, + claimed: 0, + nextWithdrawalIndex: 1, + }, + fixtureWithUser + ); + }); + describe("with solvency check at 3%", () => { + beforeEach(async () => { + const { vault } = fixture; + // Turn on insolvency check with 3% buffer + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.03")); + }); + it("Fail to allow user to create a new request due to insolvency check", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + it("Fail to allow first user to claim a withdrawal due to insolvency check", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + }); + describe("with solvency check at 10%", () => { + beforeEach(async () => { + const { vault } = fixture; + // Turn on insolvency check with 10% buffer + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.1")); + }); + it("Should allow user to create a new request", async () => { + const { vault, matt } = fixture; + + const tx = await vault + .connect(matt) + .requestWithdrawal(ousdUnits("1")); + + expect(tx) + .to.emit(vault, "WithdrawalRequested") + .withArgs(matt.address, 3, ousdUnits("1"), ousdUnits("41")); + }); + it("Should allow first user to claim the request of 10 USDC", async () => { + const { vault, daniel } = fixture; + + const tx = await vault.connect(daniel).claimWithdrawal(2); + + expect(tx) + .to.emit(vault, "WithdrawalClaimed") + .withArgs(daniel.address, 2, ousdUnits("10")); + }); + }); + }); + describe("with 99 USDC in the queue, 40 USDC in the vault, total supply 1, 1% insolvency buffer", () => { + let mockStrategy; + beforeEach(async () => { + const { governor, vault, usdc, daniel, josh, matt, strategist } = + fixture; + // Deploy a mock strategy + mockStrategy = await deployWithConfirmation("MockStrategy"); + await vault.connect(governor).approveStrategy(mockStrategy.address); + await vault.connect(governor).setDefaultStrategy(mockStrategy.address); + + // Mint USDC to users + await usdc.mintTo(daniel.address, usdcUnits("20")); + await usdc.mintTo(josh.address, usdcUnits("30")); + await usdc.mintTo(matt.address, usdcUnits("50")); + + // Approve USDC to Vault + await usdc.connect(daniel).approve(vault.address, usdcUnits("20")); + await usdc.connect(josh).approve(vault.address, usdcUnits("30")); + await usdc.connect(matt).approve(vault.address, usdcUnits("50")); + + // Mint 100 OUSD to three users + await vault.connect(daniel).mint(usdc.address, usdcUnits("20"), "0"); + await vault.connect(josh).mint(usdc.address, usdcUnits("30"), "0"); + await vault.connect(matt).mint(usdc.address, usdcUnits("50"), "0"); + + await vault.allocate(); + + // Request and claim 20 + 30 + 49 = 99 USDC from Vault + await vault.connect(daniel).requestWithdrawal(ousdUnits("20")); + await vault.connect(josh).requestWithdrawal(ousdUnits("30")); + await vault.connect(matt).requestWithdrawal(ousdUnits("49")); + + await advanceTime(delayPeriod); // Advance in time to ensure time delay between request and claim. + + // Strategist sends 40 USDC to the vault + await vault + .connect(strategist) + .withdrawFromStrategy( + mockStrategy.address, + [usdc.address], + [usdcUnits("40")] + ); - it("Should calculate redeem outputs", async () => { - const { vault, anna, usdc, ousd } = fixture; - - // OUSD total supply is 200 backed by 200 USDS - await expect( - await vault.calculateRedeemOutputs(ousdUnits("50")) - ).to.deep.equal([ - usdsUnits("50"), // USDS - BigNumber.from(0), // USDT - BigNumber.from(0), // USDC - BigNumber.from(0), // TUSD - ]); - - // Mint an additional 600 USDC, so OUSD is backed by 600 USDC and 200 USDS - // meaning 1/4 of any redeem should come from USDS and 2/3 from USDC - await usdc.connect(anna).approve(vault.address, usdcUnits("600")); - await vault.connect(anna).mint(usdc.address, usdcUnits("600"), 0); - await expect(anna).has.a.balanceOf("600", ousd); - await expect( - await vault.calculateRedeemOutputs(ousdUnits("100")) - ).to.deep.equal([ - usdsUnits("25"), // USDS - BigNumber.from(0), // USDT - usdcUnits("75"), // USDC - BigNumber.from(0), // TUSD - ]); + await vault.connect(josh).addWithdrawalQueueLiquidity(); + + // Turn on insolvency check with 10% buffer + await vault + .connect(await impersonateAndFund(await vault.governor())) + .setMaxSupplyDiff(ousdUnits("0.01")); + }); + describe("with 2 ether slashed leaving 100 - 40 - 2 = 58 USDC in the strategy", () => { + beforeEach(async () => { + const { usdc, governor } = fixture; + + // Simulate slash event of 2 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("2")); + }); + it("Should have total value of zero", async () => { + // 100 from mints - 99 outstanding withdrawals - 2 from slashing = -1 value which is rounder up to zero + expect(await fixture.vault.totalValue()).to.equal(0); + }); + it("Should have check balance of zero", async () => { + const { vault, usdc } = fixture; + // 100 from mints - 99 outstanding withdrawals - 2 from slashing = -1 value which is rounder up to zero + expect(await vault.checkBalance(usdc.address)).to.equal(0); + }); + it("Fail to allow user to create a new request due to too many outstanding requests", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + it("Fail to allow first user to claim a withdrawal due to too many outstanding requests", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + }); + describe("with 1 ether slashed leaving 100 - 40 - 1 = 59 USDC in the strategy", () => { + beforeEach(async () => { + const { usdc, governor } = fixture; + + // Simulate slash event of 1 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("1")); + }); + it("Should have total value of zero", async () => { + // 100 from mints - 99 outstanding withdrawals - 1 from slashing = 0 value + expect(await fixture.vault.totalValue()).to.equal(0); + }); + it("Fail to allow user to create a new request due to too many outstanding requests", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + it("Fail to allow first user to claim a withdrawal due to too many outstanding requests", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + }); + describe("with 0.02 ether slashed leaving 100 - 40 - 0.02 = 59.98 USDC in the strategy", () => { + beforeEach(async () => { + const { usdc, governor } = fixture; + + // Simulate slash event of 0.001 ethers + await usdc + .connect(await impersonateAndFund(mockStrategy.address)) + .transfer(governor.address, usdcUnits("0.02")); + }); + it("Should have total value of zero", async () => { + // 100 from mints - 99 outstanding withdrawals - 0.001 from slashing = 0.999 total value + expect(await fixture.vault.totalValue()).to.equal(ousdUnits("0.98")); + }); + it("Fail to allow user to create a new 1 USDC request due to too many outstanding requests", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("1")); + + await expect(tx).to.be.revertedWith("Too many outstanding requests"); + }); + + it("Fail to allow user to create a new 0.01 USDC request due to insolvency check", async () => { + const { vault, matt } = fixture; + + const tx = vault.connect(matt).requestWithdrawal(ousdUnits("0.01")); + + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + it("Fail to allow first user to claim a withdrawal due to insolvency check", async () => { + const { vault, daniel } = fixture; + + await advanceTime(delayPeriod); + + const tx = vault.connect(daniel).claimWithdrawal(2); + + // diff = 1 total supply / 0.98 assets = 1.020408163265306122 which is > 1 maxSupplyDiff + await expect(tx).to.be.revertedWith("Backing supply liquidity error"); + }); + }); + }); }); }); diff --git a/contracts/test/vault/upgrade.js b/contracts/test/vault/upgrade.js deleted file mode 100644 index 8044bc26b3..0000000000 --- a/contracts/test/vault/upgrade.js +++ /dev/null @@ -1,27 +0,0 @@ -const { expect } = require("chai"); - -const { loadDefaultFixture } = require("../_fixture"); - -describe("VaultAdmin Upgrades", async function () { - let ousd, vault, governor; - - beforeEach(async function () { - const fixture = await loadDefaultFixture(); - vault = fixture.vault; - ousd = fixture.ousd; - governor = fixture.governor; - }); - - it("should upgrade to a new admin implementation", async function () { - const newVaultImpl = ousd.address; // ;) - await vault.connect(governor).setAdminImpl(newVaultImpl); - expect(await vault.ADMIN_IMPLEMENTATION()).to.eq(newVaultImpl); - }); - - it("should not upgrade to a non-contract admin implementation", async function () { - const blankImpl = "0x4000000000000000000000000000000000000004"; - await expect( - vault.connect(governor).setAdminImpl(blankImpl) - ).to.be.revertedWith("new implementation is not a contract"); - }); -}); diff --git a/contracts/test/vault/vault.mainnet.fork-test.js b/contracts/test/vault/vault.mainnet.fork-test.js index 0b30da267b..afeaccf5a8 100644 --- a/contracts/test/vault/vault.mainnet.fork-test.js +++ b/contracts/test/vault/vault.mainnet.fork-test.js @@ -1,5 +1,4 @@ const { expect } = require("chai"); -const { utils } = require("ethers"); const addresses = require("../../utils/addresses"); const { loadDefaultFixture } = require("./../_fixture"); @@ -9,16 +8,12 @@ const { differenceInStrategyBalance, differenceInErc20TokenBalances, isCI, - decimalsFor, } = require("./../helpers"); const { canWithdrawAllFromMorphoOUSD } = require("../../utils/morpho"); const { impersonateAndFund } = require("../../utils/signers"); const { shouldHaveRewardTokensConfigured, } = require("./../behaviour/reward-tokens.fork"); -const { formatUnits } = require("ethers/lib/utils"); - -const log = require("../../utils/logger")("test:fork:ousd:vault"); /** * Regarding hardcoded addresses: @@ -76,11 +71,13 @@ describe("ForkTest: Vault", function () { ); }); - it("Should have the correct OUSD MetaStrategy address set", async () => { + it("Should have the OUSD/USDC AMO mint whitelist", async () => { const { vault } = fixture; - expect(await vault.ousdMetaStrategy()).to.equal( - addresses.mainnet.CurveOUSDAMOStrategy - ); + expect( + await vault.isMintWhitelistedStrategy( + addresses.mainnet.CurveOUSDAMOStrategy + ) + ).to.be.true; }); it("Should have supported assets", async () => { @@ -110,7 +107,7 @@ describe("ForkTest: Vault", function () { expect(await vault.capitalPaused()).to.be.false; }); - it("Should allow to mint and redeem w/ USDC", async () => { + it("Should allow to mint w/ USDC", async () => { const { ousd, vault, josh, usdc } = fixture; const balancePreMint = await ousd .connect(josh) @@ -123,37 +120,6 @@ describe("ForkTest: Vault", function () { const balanceDiff = balancePostMint.sub(balancePreMint); expect(balanceDiff).to.approxEqualTolerance(ousdUnits("500"), 1); - - await vault.connect(josh).redeem(balanceDiff, 0); - - const balancePostRedeem = await ousd - .connect(josh) - .balanceOf(josh.getAddress()); - expect(balancePreMint).to.approxEqualTolerance(balancePostRedeem, 1); - }); - - it("Should calculate and return redeem outputs", async () => { - const { vault } = fixture; - const outputs = await vault.calculateRedeemOutputs(ousdUnits("100")); - expect(outputs).to.have.length(1); - const assets = await vault.getAllAssets(); - - const values = await Promise.all( - outputs.map(async (output, index) => { - const asset = await ethers.getContractAt( - "MintableERC20", - assets[index] - ); - return parseFloat( - formatUnits(output.toString(), await decimalsFor(asset)) - ); - }) - ); - - expect(ousdUnits(values[0].toString())).to.approxEqualTolerance( - ousdUnits("100"), - 0.5 - ); }); it("should withdraw from and deposit to strategy", async () => { @@ -220,34 +186,6 @@ describe("ForkTest: Vault", function () { }); }); - describe("Oracle", () => { - it("Should have correct Price Oracle address set", async () => { - const { vault } = fixture; - expect(await vault.priceProvider()).to.equal( - "0x36CFB852d3b84afB3909BCf4ea0dbe8C82eE1C3c" - ); - }); - - it("Should return a price for minting with USDC", async () => { - const { vault, usdc } = fixture; - const price = await vault.priceUnitMint(usdc.address); - - log(`Price for minting with USDC: ${utils.formatEther(price, 6)}`); - - expect(price).to.be.lte(utils.parseEther("1")); - expect(price).to.be.gt(utils.parseEther("0.999")); - }); - - it("Should return a price for redeem with USDC", async () => { - const { vault, usdc } = fixture; - const price = await vault.priceUnitRedeem(usdc.address); - - log(`Price for redeeming with USDC: ${utils.formatEther(price, 6)}`); - - expect(price).to.be.gte(utils.parseEther("1")); - }); - }); - describe("Assets & Strategies", () => { it("Should NOT have any unknown assets", async () => { const { vault } = fixture; @@ -292,12 +230,12 @@ describe("ForkTest: Vault", function () { } }); - it("Should have correct default strategy set for USDC", async () => { - const { vault, usdc } = fixture; + it("Should have correct default strategy", async () => { + const { vault } = fixture; - expect([ - "0x3643cafA6eF3dd7Fcc2ADaD1cabf708075AFFf6e", // Morpho OUSD v2 Strategy - ]).to.include(await vault.assetDefaultStrategies(usdc.address)); + expect(await vault.defaultStrategy()).to.equal( + "0x3643cafA6eF3dd7Fcc2ADaD1cabf708075AFFf6e" // Morpho OUSD v2 Strategy + ); }); it("Should be able to withdraw from all strategies", async () => { diff --git a/contracts/test/vault/vault.sonic.fork-test.js b/contracts/test/vault/vault.sonic.fork-test.js index f1deaef205..526f1e1273 100644 --- a/contracts/test/vault/vault.sonic.fork-test.js +++ b/contracts/test/vault/vault.sonic.fork-test.js @@ -79,11 +79,6 @@ describe("ForkTest: Sonic Vault", function () { addresses.multichainBuybackOperator ); }); - - it("Should have redeem fee set to 0.1%", async () => { - const { oSonicVault } = fixture; - expect(await oSonicVault.redeemFeeBps()).to.equal(BigNumber.from("10")); - }); }); describe("Rebase", () => { diff --git a/contracts/test/vault/z_mockvault.js b/contracts/test/vault/z_mockvault.js index bc28471a85..7a21a21206 100644 --- a/contracts/test/vault/z_mockvault.js +++ b/contracts/test/vault/z_mockvault.js @@ -50,10 +50,7 @@ describe("Vault mock with rebase", async () => { const promise = expect( mockVault .connect(matt) - .redeem( - utils.parseUnits(`${redeemAmount}`, 18), - utils.parseUnits(`${redeemAmount}`, 18) - ) + .requestWithdrawal(utils.parseUnits(`${redeemAmount}`, 18)) ); if (revertMessage) {