diff --git a/contracts/contracts/harvest/FixedRateDripper.sol b/contracts/contracts/harvest/FixedRateDripper.sol index 2d96a065b2..4b52cbec51 100644 --- a/contracts/contracts/harvest/FixedRateDripper.sol +++ b/contracts/contracts/harvest/FixedRateDripper.sol @@ -58,7 +58,7 @@ contract FixedRateDripper is Dripper { * @param _perSecond Rate of WETH to drip per second */ function setDripRate(uint192 _perSecond) external onlyGovernorOrStrategist { - emit DripRateUpdated(_perSecond, drip.perSecond); + emit DripRateUpdated(drip.perSecond, _perSecond); /** * Note: It's important to call `_collect` before updating diff --git a/contracts/contracts/interfaces/IVault.sol b/contracts/contracts/interfaces/IVault.sol index e1c4e78160..2c99fa2583 100644 --- a/contracts/contracts/interfaces/IVault.sol +++ b/contracts/contracts/interfaces/IVault.sol @@ -264,4 +264,8 @@ interface IVault { external view returns (bool); + + function withdrawalClaimDelay() external view returns (uint256); + + function setWithdrawalClaimDelay(uint256 newDelay) external; } diff --git a/contracts/contracts/vault/OETHBaseVaultCore.sol b/contracts/contracts/vault/OETHBaseVaultCore.sol index 4e2ab6fc6b..8a57068669 100644 --- a/contracts/contracts/vault/OETHBaseVaultCore.sol +++ b/contracts/contracts/vault/OETHBaseVaultCore.sol @@ -81,33 +81,22 @@ contract OETHBaseVaultCore is OETHVaultCore { // @inheritdoc OETHVaultCore // solhint-disable-next-line no-unused-vars function requestWithdrawal(uint256 _amount) - external + public virtual override - returns (uint256, uint256) + returns (uint256 requestId, uint256 queued) { - revert("Async withdrawals disabled"); + require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); + return super.requestWithdrawal(_amount); } - // @inheritdoc OETHVaultCore - // solhint-disable-next-line no-unused-vars - function claimWithdrawal(uint256 _requestId) - external - virtual - override - returns (uint256) - { - revert("Async withdrawals disabled"); - } - - // @inheritdoc OETHVaultCore - // solhint-disable-next-line no-unused-vars - function claimWithdrawals(uint256[] memory _requestIds) - external + function _claimWithdrawal(uint256 requestId) + internal virtual override - returns (uint256[] memory, uint256) + returns (uint256 amount) { - revert("Async withdrawals disabled"); + require(withdrawalClaimDelay > 0, "Async withdrawals not enabled"); + return _claimWithdrawal(requestId, withdrawalClaimDelay); } } diff --git a/contracts/contracts/vault/OETHVaultCore.sol b/contracts/contracts/vault/OETHVaultCore.sol index 07c391824f..bf62ce71da 100644 --- a/contracts/contracts/vault/OETHVaultCore.sol +++ b/contracts/contracts/vault/OETHVaultCore.sol @@ -166,7 +166,7 @@ contract OETHVaultCore is VaultCore { * @param queued Cumulative total of all WETH queued including already claimed requests. */ function requestWithdrawal(uint256 _amount) - external + public virtual whenNotCapitalPaused nonReentrant @@ -284,6 +284,14 @@ contract OETHVaultCore is VaultCore { } function _claimWithdrawal(uint256 requestId) + internal + virtual + returns (uint256 amount) + { + return _claimWithdrawal(requestId, CLAIM_DELAY); + } + + function _claimWithdrawal(uint256 requestId, uint256 claimDelay) internal returns (uint256 amount) { @@ -292,7 +300,7 @@ contract OETHVaultCore is VaultCore { WithdrawalQueueMetadata memory queue = withdrawalQueueMetadata; require( - request.timestamp + CLAIM_DELAY <= block.timestamp, + request.timestamp + claimDelay <= block.timestamp, "Claim delay not met" ); // If there isn't enough reserved liquidity in the queue to claim diff --git a/contracts/contracts/vault/VaultAdmin.sol b/contracts/contracts/vault/VaultAdmin.sol index 0bc783a766..9dcf3294f9 100644 --- a/contracts/contracts/vault/VaultAdmin.sol +++ b/contracts/contracts/vault/VaultAdmin.sol @@ -163,6 +163,20 @@ contract VaultAdmin is VaultStorage { emit DripperChanged(_dripper); } + /** + * @notice Changes the async withdrawal claim period for superOETHb + * @param _delay Delay period (should be between 30mins to 7 days). + * Set to 0 to disable async withdrawals + */ + function setWithdrawalClaimDelay(uint256 _delay) external onlyGovernor { + require( + _delay == 0 || (_delay >= 30 minutes && _delay <= 7 days), + "Invalid claim delay period" + ); + emit WithdrawalClaimDelayUpdated(withdrawalClaimDelay, _delay); + withdrawalClaimDelay = _delay; + } + /*************************************** Swaps ****************************************/ diff --git a/contracts/contracts/vault/VaultStorage.sol b/contracts/contracts/vault/VaultStorage.sol index 8b63159164..5c3f7fa59a 100644 --- a/contracts/contracts/vault/VaultStorage.sol +++ b/contracts/contracts/vault/VaultStorage.sol @@ -68,6 +68,7 @@ contract VaultStorage is Initializable, Governable { uint256 _amount ); event WithdrawalClaimable(uint256 _claimable, uint256 _newClaimable); + event WithdrawalClaimDelayUpdated(uint256 _oldDelay, uint256 _newDelay); // Assets supported by the Vault, i.e. Stablecoins enum UnitConversion { @@ -236,8 +237,11 @@ contract VaultStorage is Initializable, Governable { /// @notice Mapping of withdrawal request indices to the user withdrawal request data mapping(uint256 => WithdrawalRequest) public withdrawalRequests; + /// @notice Used for superOETHb async withdrawal + uint256 public withdrawalClaimDelay; + // For future use - uint256[45] private __gap; + uint256[44] private __gap; /** * @notice set the implementation for the admin, this needs to be in a base class else we cannot set it diff --git a/contracts/deploy/base/018_async_withdrawals.js b/contracts/deploy/base/018_async_withdrawals.js new file mode 100644 index 0000000000..5265306dcf --- /dev/null +++ b/contracts/deploy/base/018_async_withdrawals.js @@ -0,0 +1,45 @@ +const { deployOnBaseWithGuardian } = require("../../utils/deploy-l2"); +const { deployWithConfirmation } = require("../../utils/deploy"); +const addresses = require("../../utils/addresses"); + +module.exports = deployOnBaseWithGuardian( + { + deployName: "018_async_withdrawals", + }, + async ({ ethers }) => { + const cOETHbVaultProxy = await ethers.getContract("OETHBaseVaultProxy"); + const cOETHbVault = await ethers.getContractAt( + "IVault", + cOETHbVaultProxy.address + ); + + // Deploy new implementation + const dOETHbVaultCore = await deployWithConfirmation("OETHBaseVaultCore", [ + addresses.base.WETH, + ]); + const dOETHbVaultAdmin = await deployWithConfirmation("OETHBaseVaultAdmin"); + + return { + actions: [ + { + // 1. Upgrade VaultCore + contract: cOETHbVaultProxy, + signature: "upgradeTo(address)", + args: [dOETHbVaultCore.address], + }, + { + // 2. Upgrade VaultAdmin + contract: cOETHbVault, + signature: "setAdminImpl(address)", + args: [dOETHbVaultAdmin.address], + }, + { + // 3. Set async claim delay to 1 day + contract: cOETHbVault, + signature: "setWithdrawalClaimDelay(uint256)", + args: [24 * 60 * 60], // 1d + }, + ], + }; + } +); diff --git a/contracts/test/vault/oethb-vault.base.fork-test.js b/contracts/test/vault/oethb-vault.base.fork-test.js index 7b9be983f3..462a25c26c 100644 --- a/contracts/test/vault/oethb-vault.base.fork-test.js +++ b/contracts/test/vault/oethb-vault.base.fork-test.js @@ -3,7 +3,7 @@ const { defaultBaseFixture } = require("../_fixture-base"); const { expect } = require("chai"); const addresses = require("../../utils/addresses"); const { impersonateAndFund } = require("../../utils/signers"); -const { oethUnits } = require("../helpers"); +const { oethUnits, advanceTime } = require("../helpers"); const { deployWithConfirmation } = require("../../utils/deploy"); const baseFixture = createFixtureLoader(defaultBaseFixture); @@ -14,14 +14,14 @@ describe("ForkTest: OETHb Vault", function () { fixture = await baseFixture(); }); - describe("Mint & Permissioned redeems", function () { - async function _mint(signer) { - const { weth, oethbVault } = fixture; - await weth.connect(signer).deposit({ value: oethUnits("1") }); - await weth.connect(signer).approve(oethbVault.address, oethUnits("1")); - await oethbVault.connect(signer).mint(weth.address, oethUnits("1"), "0"); - } + async function _mint(signer) { + const { weth, oethbVault } = fixture; + await weth.connect(signer).deposit({ value: oethUnits("1") }); + await weth.connect(signer).approve(oethbVault.address, oethUnits("1")); + await oethbVault.connect(signer).mint(weth.address, oethUnits("1"), "0"); + } + describe("Mint & Permissioned redeems", function () { it("Should allow anyone to mint", async () => { const { nick, weth, oethb, oethbVault } = fixture; @@ -113,20 +113,28 @@ describe("ForkTest: OETHb Vault", function () { }); describe("Async withdrawals", function () { - it("Should be disabled", async () => { - const { oethbVault, nick } = fixture; + it("Should allow 1:1 async withdrawals", async function () { + const { rafael, oethbVault } = fixture; - let tx = oethbVault.connect(nick).requestWithdrawal(oethUnits("1")); + const delayPeriod = await oethbVault.withdrawalClaimDelay(); - await expect(tx).to.be.revertedWith("Async withdrawals disabled"); + if (delayPeriod == 0) { + // Skip when disabled + return; + } - tx = oethbVault.connect(nick).claimWithdrawal(oethUnits("1")); + const { nextWithdrawalIndex: requestId } = + await oethbVault.withdrawalQueueMetadata(); - await expect(tx).to.be.revertedWith("Async withdrawals disabled"); + // Rafael mints 1 superOETHb + await _mint(rafael); - tx = oethbVault.connect(nick).claimWithdrawals([oethUnits("1")]); + // Rafael places an async withdrawal request + await oethbVault.connect(rafael).requestWithdrawal(oethUnits("1")); - await expect(tx).to.be.revertedWith("Async withdrawals disabled"); + // ... and tries to claim it after 1d + await advanceTime(delayPeriod); + await oethbVault.connect(rafael).claimWithdrawal(requestId); }); });