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
-
+
-## OUSD Vault
+## Vault
-## Vault Core Squashed
+## Vault Squashed
-
+
### Storage

-
-## Vault Admin Squashed
-
-
-
-## OETH Vault
-
-## Vault Core Squashed
-
-
-
-## Vault Admin Squashed
-
-
-
-### Storage
-
-
-
-## Base OETH Vault
-
-## Vault Core Squashed
-
-
-
-## Vault Admin Squashed
-
-
-
-### Storage
-
-
-
-## Origin Sonic (OS) Vault
-
-## Vault Core Squashed
-
-
-
-## Vault Admin Squashed
-
-
-
-### Storage
-
-
\ 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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-
-
-
-
-
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 @@
-