From a35ecf1d7896003260b24513cd00430d7cb635e2 Mon Sep 17 00:00:00 2001 From: t11s Date: Wed, 10 Nov 2021 00:50:39 -0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20VaultRouterModule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 10 +- src/interfaces/AllowedPermit.sol | 24 ++ src/modules/VaultETHWrapperModule.sol | 108 --------- src/modules/VaultRouterModule.sol | 307 ++++++++++++++++++++++++++ src/test/VaultETHWrapperModule.t.sol | 93 -------- src/test/VaultRouterModule.t.sol | 103 +++++++++ 6 files changed, 439 insertions(+), 206 deletions(-) create mode 100644 src/interfaces/AllowedPermit.sol delete mode 100644 src/modules/VaultETHWrapperModule.sol create mode 100644 src/modules/VaultRouterModule.sol delete mode 100644 src/test/VaultETHWrapperModule.t.sol create mode 100644 src/test/VaultRouterModule.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index c659d5e..ae8d6fa 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -28,10 +28,10 @@ testReplaceWithdrawalQueueIndexWithTip() (gas: 127642) testFailTrustStrategyWithWrongUnderlying() (gas: 2002469) testSwapWithdrawalQueueIndexes() (gas: 127920) testFailWithdrawWithEmptyQueue() (gas: 313475) -testFailDepositIntoNotWETHVault() (gas: 10760) -testFailRedeemFromNotWETHVault() (gas: 245367) -testAtomicDepositRedeemETH() (gas: 330054) -testFailWithdrawFromNotWETHVault() (gas: 245301) -testAtomicDepositWithdrawETH() (gas: 331727) testFailNoDuplicateVaults() (gas: 272747603293004) testDeployVault() (gas: 4167465) +testFailDepositIntoNotWETHVault() (gas: 10797) +testFailRedeemFromNotWETHVault() (gas: 247258) +testAtomicDepositRedeemETH() (gas: 332003) +testFailWithdrawFromNotWETHVault() (gas: 247181) +testAtomicDepositWithdrawETH() (gas: 333639) diff --git a/src/interfaces/AllowedPermit.sol b/src/interfaces/AllowedPermit.sol new file mode 100644 index 0000000..893f2ad --- /dev/null +++ b/src/interfaces/AllowedPermit.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.9; + +interface AllowedPermit { + /// @dev Non-standard permit interface used by DAI. + /// @param holder The address of the token owner. + /// @param spender The address of the token spender. + /// @param nonce The owner's nonce, increases at each call to permit. + /// @param expiry The timestamp at which the permit is no longer valid. + /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0. + /// @param v Must produce valid secp256k1 signature from the owner along with r and s. + /// @param r Must produce valid secp256k1 signature from the owner along with v and s. + /// @param s Must produce valid secp256k1 signature from the owner along with r and v. + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} diff --git a/src/modules/VaultETHWrapperModule.sol b/src/modules/VaultETHWrapperModule.sol deleted file mode 100644 index c60ca30..0000000 --- a/src/modules/VaultETHWrapperModule.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.9; - -import {WETH} from "solmate/tokens/WETH.sol"; -import {ERC20} from "solmate/tokens/ERC20.sol"; -import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; -import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; - -import {Vault} from "../Vault.sol"; - -/// @title Rari Vault ETH Wrapper Module -/// @author Transmissions11 and JetJadeja -/// @notice Module for using ETH with a WETH Vault. -contract VaultETHWrapperModule { - using SafeTransferLib for ERC20; - using SafeTransferLib for address; - using FixedPointMathLib for uint256; - - /// @notice Deposit ETH into a WETH compatible Vault. - /// @param vault The WETH compatible Vault to deposit into. - /// @dev The caller must attach the amount they want to deposit as msg.value. - function depositETHIntoVault(Vault vault) external payable { - // Ensure the Vault's underlying token is WETH compatible. - require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); - - // Get the Vault's underlying as WETH. - WETH weth = WETH(payable(address(vault.UNDERLYING()))); - - // Wrap the ETH into WETH. - weth.deposit{value: msg.value}(); - - // Approve the WETH to the Vault. - weth.approve(address(vault), msg.value); - - // Deposit the WETH into the Vault. - vault.deposit(msg.value); - - // Get the Vault's rvToken. - ERC20 rvToken = ERC20(vault); - - // Transfer the newly minted rvTokens back to the user. - rvToken.transfer(msg.sender, rvToken.balanceOf(address(this))); - } - - /// @notice Withdraw ETH from a WETH compatible Vault. - /// @param vault The WETH compatible Vault to withdraw from. - /// @param underlyingAmount The amount of ETH to withdraw from the Vault. - /// @dev The caller must approve the equivalent amount of rvTokens to the module. - function withdrawETHFromVault(Vault vault, uint256 underlyingAmount) external { - // Ensure the Vault's underlying token is WETH compatible. - require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); - - // Compute the amount of rvTokens equivalent to the underlying amount. - // We know the Vault's base unit is 1e18 as it's required if underlyingIsWETH returns true. - uint256 rvTokenAmount = underlyingAmount.fdiv(vault.exchangeRate(), 1e18); - - // Get the Vault's rvToken. - ERC20 rvToken = ERC20(vault); - - // Transfer in the equivalent amount of rvTokens from the caller. - rvToken.safeTransferFrom(msg.sender, address(this), rvTokenAmount); - - // Withdraw from the Vault. - vault.withdraw(underlyingAmount); - - // Get the Vault's underlying as WETH. - WETH weth = WETH(payable(address(vault.UNDERLYING()))); - - // Convert the WETH into ETH. - weth.withdraw(underlyingAmount); - - // Transfer the unwrapped ETH to the caller. - msg.sender.safeTransferETH(underlyingAmount); - } - - /// @notice Redeem ETH from a WETH compatible Vault. - /// @param vault The WETH compatible Vault to redeem from. - /// @param rvTokenAmount The amount of rvTokens to withdraw from the Vault. - /// @dev The caller must approve the provided amount of rvTokens to the module. - function redeemETHFromVault(Vault vault, uint256 rvTokenAmount) external { - // Ensure the Vault accepts WETH. - require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); - - // Get the Vault's rvToken. - ERC20 rvToken = ERC20(vault); - - // Transfer in the rvTokens from the caller. - rvToken.safeTransferFrom(msg.sender, address(this), rvTokenAmount); - - // Redeem the rvTokens. - vault.redeem(rvTokenAmount); - - // Get the Vault's underlying as WETH. - WETH weth = WETH(payable(address(vault.UNDERLYING()))); - - // Get how much WETH we redeemed. - uint256 withdrawnWETH = weth.balanceOf(address(this)); - - // Convert the WETH into ETH. - weth.withdraw(withdrawnWETH); - - // Transfer the unwrapped ETH to the caller. - msg.sender.safeTransferETH(withdrawnWETH); - } - - /// @dev Required for the module to receive unwrapped ETH. - receive() external payable {} -} diff --git a/src/modules/VaultRouterModule.sol b/src/modules/VaultRouterModule.sol new file mode 100644 index 0000000..6ab122a --- /dev/null +++ b/src/modules/VaultRouterModule.sol @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.9; + +import {WETH} from "solmate/tokens/WETH.sol"; +import {ERC20} from "solmate/tokens/ERC20.sol"; +import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; +import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; + +import {AllowedPermit} from "../interfaces/AllowedPermit.sol"; + +import {Vault} from "../Vault.sol"; + +/// @title Rari Vault Router Module +/// @author Transmissions11 and JetJadeja +/// @notice Module that enables depositing ETH into WETH compatible Vaults +/// and approval-free deposits into Vaults with permit compatible underlying. +contract VaultRouterModule { + using SafeTransferLib for ERC20; + using SafeTransferLib for address; + using FixedPointMathLib for uint256; + + /*/////////////////////////////////////////////////////////////// + DEPOSIT LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice Deposit ETH into a WETH compatible Vault. + /// @param vault The WETH compatible Vault to deposit into. + function depositETHIntoVault(Vault vault) external payable { + // Ensure the Vault's underlying is stored as WETH compatible. + require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); + + // Get the Vault's underlying as WETH. + WETH weth = WETH(payable(address(vault.UNDERLYING()))); + + // Wrap the ETH into WETH. + weth.deposit{value: msg.value}(); + + // Deposit and transfer the minted rvTokens back to the caller. + depositIntoVaultForCaller(vault, weth, msg.value); + } + + /// @notice Deposits into a Vault, transferring in its underlying token from the caller via permit. + /// @param vault The Vault to deposit into. + /// @param underlyingAmount The amount of underlying tokens to deposit into the Vault. + /// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp. + /// @param v Must produce valid secp256k1 signature from the caller along with r and s. + /// @param r Must produce valid secp256k1 signature from the caller along with v and s. + /// @param s Must produce valid secp256k1 signature from the caller along with r and v. + /// @dev Use depositIntoVaultWithAllowedPermit for tokens using DAI's non-standard permit interface. + function depositIntoVaultWithPermit( + Vault vault, + uint256 underlyingAmount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + // Get the Vault's underlying token. + ERC20 underlying = vault.UNDERLYING(); + + // Transfer in the provided amount of underlying tokens from the caller via permit. + permitAndTransferFromCaller(underlying, underlyingAmount, deadline, v, r, s); + + // Deposit and transfer the minted rvTokens back to the caller. + depositIntoVaultForCaller(vault, underlying, underlyingAmount); + } + + /// @notice Deposits into a Vault, transferring in its underlying token from the caller via allowed permit. + /// @param vault The Vault to deposit into. + /// @param underlyingAmount The amount of underlying tokens to deposit into the Vault. + /// @param nonce The callers's nonce, increases at each call to permit. + /// @param expiry The timestamp at which the permit is no longer valid. + /// @param v Must produce valid secp256k1 signature from the caller along with r and s. + /// @param r Must produce valid secp256k1 signature from the caller along with v and s. + /// @param s Must produce valid secp256k1 signature from the caller along with r and v. + /// @dev Alternative to depositIntoVaultWithPermit for tokens using DAI's non-standard permit interface. + function depositIntoVaultWithAllowedPermit( + Vault vault, + uint256 underlyingAmount, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external { + // Get the Vault's underlying token. + ERC20 underlying = vault.UNDERLYING(); + + // Transfer in the provided amount of underlying tokens from the caller via allowed permit. + allowedPermitAndTransferFromCaller(underlying, underlyingAmount, nonce, expiry, v, r, s); + + // Deposit and transfer the minted rvTokens back to the caller. + depositIntoVaultForCaller(vault, underlying, underlyingAmount); + } + + /*/////////////////////////////////////////////////////////////// + WITHDRAWAL LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice Withdraw ETH from a WETH compatible Vault. + /// @param vault The WETH compatible Vault to withdraw from. + /// @param underlyingAmount The amount of ETH to withdraw from the Vault. + function withdrawETHFromVault(Vault vault, uint256 underlyingAmount) external { + // Ensure the Vault's underlying is stored as WETH compatible. + require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); + + // Compute the amount of rvTokens equivalent to the underlying amount. + // We know the Vault's base unit is 1e18 as it's required if underlyingIsWETH returns true. + uint256 rvTokenAmount = underlyingAmount.fdiv(vault.exchangeRate(), 1e18); + + // Transfer in the equivalent amount of rvTokens from the caller. + ERC20(vault).safeTransferFrom(msg.sender, address(this), rvTokenAmount); + + // Withdraw from the Vault. + vault.withdraw(underlyingAmount); + + // Unwrap the withdrawn amount of WETH and transfer it to the caller. + unwrapAndTransfer(WETH(payable(address(vault.UNDERLYING()))), underlyingAmount); + } + + /// @notice Withdraw ETH from a WETH compatible Vault. + /// @param vault The WETH compatible Vault to withdraw from. + /// @param underlyingAmount The amount of ETH to withdraw from the Vault. + /// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp. + /// @param v Must produce valid secp256k1 signature from the caller along with r and s. + /// @param r Must produce valid secp256k1 signature from the caller along with v and s. + /// @param s Must produce valid secp256k1 signature from the caller along with r and v. + function withdrawETHFromVaultWithPermit( + Vault vault, + uint256 underlyingAmount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + // Ensure the Vault's underlying is stored as WETH compatible. + require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); + + // Compute the amount of rvTokens equivalent to the underlying amount. + // We know the Vault's base unit is 1e18 as it's required if underlyingIsWETH returns true. + uint256 rvTokenAmount = underlyingAmount.fdiv(vault.exchangeRate(), 1e18); + + // Transfer in the equivalent amount of rvTokens from the caller via permit. + permitAndTransferFromCaller(vault, rvTokenAmount, deadline, v, r, s); + + // Withdraw from the Vault. + vault.withdraw(underlyingAmount); + + // Unwrap the withdrawn amount of WETH and transfer it to the caller. + unwrapAndTransfer(WETH(payable(address(vault.UNDERLYING()))), underlyingAmount); + } + + /*/////////////////////////////////////////////////////////////// + REDEEM LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice Redeem ETH from a WETH compatible Vault. + /// @param vault The WETH compatible Vault to redeem from. + /// @param rvTokenAmount The amount of rvTokens to withdraw from the Vault. + function redeemETHFromVault(Vault vault, uint256 rvTokenAmount) external { + // Ensure the Vault's underlying is stored as WETH compatible. + require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); + + // Transfer in the provided amount of rvTokens from the caller. + ERC20(vault).safeTransferFrom(msg.sender, address(this), rvTokenAmount); + + // Redeem the rvTokens. + vault.redeem(rvTokenAmount); + + // Get the Vault's underlying as WETH. + WETH weth = WETH(payable(address(vault.UNDERLYING()))); + + // Unwrap all our WETH and transfer it to the caller. + unwrapAndTransfer(weth, weth.balanceOf(address(this))); + } + + /// @notice Redeem ETH from a WETH compatible Vault. + /// @param vault The WETH compatible Vault to redeem from. + /// @param rvTokenAmount The amount of rvTokens to withdraw from the Vault. + /// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp. + /// @param v Must produce valid secp256k1 signature from the caller along with r and s. + /// @param r Must produce valid secp256k1 signature from the caller along with v and s. + /// @param s Must produce valid secp256k1 signature from the caller along with r and v. + function redeemETHFromVaultWithPermit( + Vault vault, + uint256 rvTokenAmount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + // Ensure the Vault's underlying is stored as WETH compatible. + require(vault.underlyingIsWETH(), "UNDERLYING_NOT_WETH"); + + // Transfer in the provided amount of rvTokens from the caller via permit. + permitAndTransferFromCaller(vault, rvTokenAmount, deadline, v, r, s); + + // Redeem the rvTokens. + vault.redeem(rvTokenAmount); + + // Get the Vault's underlying as WETH. + WETH weth = WETH(payable(address(vault.UNDERLYING()))); + + // Unwrap all our WETH and transfer it to the caller. + unwrapAndTransfer(weth, weth.balanceOf(address(this))); + } + + /*/////////////////////////////////////////////////////////////// + WETH UNWRAPPING LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @dev Unwraps the provided amount of WETH and transfers it to the caller. + /// @param weth The WETH contract to withdraw the amount from. + /// @param amount The amount of WETH to unwrap into ETH and transfer. + function unwrapAndTransfer(WETH weth, uint256 amount) internal { + // Convert the WETH into ETH. + weth.withdraw(amount); + + // Transfer the unwrapped ETH to the caller. + msg.sender.safeTransferETH(amount); + } + + /*/////////////////////////////////////////////////////////////// + VAULT DEPOSIT LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @dev Approves tokens, deposits them into a Vault + /// and transfers the minted rvTokens back to the caller. + /// @param vault The Vault to deposit into. + /// @param underlying The underlying token the Vault accepts. + /// @param amount The minimum amount that must be approved. + function depositIntoVaultForCaller( + Vault vault, + ERC20 underlying, + uint256 amount + ) internal { + // If we don't have enough of the underlying token approved already: + if (amount > underlying.allowance(address(this), address(vault))) { + // Approve an unlimited amount of the underlying token to the Vault. + underlying.safeApprove(address(vault), type(uint256).max); + } + + // Deposit the underlying tokens into the Vault. + vault.deposit(amount); + + // Transfer the newly minted rvTokens back to the caller. + ERC20(vault).safeTransfer(msg.sender, vault.balanceOf(address(this))); + } + + /*/////////////////////////////////////////////////////////////// + PERMIT LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @dev Permits tokens from the caller and transfers them into the module. + /// @param token The token to permit and transfer in. + /// @param amount The amount of tokens permit and transfer in. + /// @param deadline A timestamp, the block's timestamp must be less than or equal to this timestamp. + /// @param v Must produce valid secp256k1 signature from the caller along with r and s. + /// @param r Must produce valid secp256k1 signature from the caller along with v and s. + /// @param s Must produce valid secp256k1 signature from the caller along with r and v. + function permitAndTransferFromCaller( + ERC20 token, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + // Approve the tokens from the caller to the module via permit. + token.permit(msg.sender, address(this), amount, deadline, v, r, s); + + // Transfer the tokens from the caller to the module. + token.safeTransferFrom(msg.sender, address(this), amount); + } + + /// @dev Max permits tokens from the caller and transfers them into the module. + /// @param token The token to permit and transfer in. + /// @param amount The amount of tokens permit and transfer in. + /// @param nonce The callers's nonce, increases at each call to permit. + /// @param expiry The timestamp at which the permit is no longer valid. + /// @param v Must produce valid secp256k1 signature from the caller along with r and s. + /// @param r Must produce valid secp256k1 signature from the caller along with v and s. + /// @param s Must produce valid secp256k1 signature from the caller along with r and v. + /// @dev Alternative to permitAndTransferFromCaller for tokens using DAI's non-standard permit interface. + function allowedPermitAndTransferFromCaller( + ERC20 token, + uint256 amount, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + // Approve the tokens from the caller to the module via DAI's non-standard permit. + AllowedPermit(address(token)).permit(msg.sender, address(this), nonce, expiry, true, v, r, s); + + // Transfer the tokens from the caller to the module. + token.safeTransferFrom(msg.sender, address(this), amount); + } + + /*/////////////////////////////////////////////////////////////// + RECIEVE ETHER LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @dev Required for the module to receive unwrapped ETH. + receive() external payable {} +} diff --git a/src/test/VaultETHWrapperModule.t.sol b/src/test/VaultETHWrapperModule.t.sol deleted file mode 100644 index 93143a2..0000000 --- a/src/test/VaultETHWrapperModule.t.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.9; - -import {WETH} from "solmate/tokens/WETH.sol"; - -import {DSTestPlus} from "./utils/DSTestPlus.sol"; - -import {VaultETHWrapperModule} from "../modules/VaultETHWrapperModule.sol"; - -import {Vault} from "../Vault.sol"; -import {VaultFactory} from "../VaultFactory.sol"; - -contract VaultETHWrapperModuleTest is DSTestPlus { - Vault vault; - WETH underlying; - - VaultETHWrapperModule vaultETHWrapperModule; - - function setUp() public { - underlying = new WETH(); - vault = new VaultFactory().deployVault(underlying); - - vault.initialize(); - - vaultETHWrapperModule = new VaultETHWrapperModule(); - } - - function testAtomicDepositWithdrawETH() public { - vault.setUnderlyingIsWETH(true); - - uint256 startingETHBal = address(this).balance; - - vaultETHWrapperModule.depositETHIntoVault{value: 1 ether}(vault); - - assertEq(address(this).balance, startingETHBal - 1 ether); - - assertEq(vault.balanceOf(address(this)), 1e18); - assertEq(vault.balanceOfUnderlying(address(this)), 1 ether); - - vault.approve(address(vaultETHWrapperModule), 1e18); - vaultETHWrapperModule.withdrawETHFromVault(vault, 1 ether); - - assertEq(address(this).balance, startingETHBal); - } - - function testAtomicDepositRedeemETH() public { - vault.setUnderlyingIsWETH(true); - - uint256 startingETHBal = address(this).balance; - - vaultETHWrapperModule.depositETHIntoVault{value: 69 ether}(vault); - - assertEq(address(this).balance, startingETHBal - 69 ether); - - assertEq(vault.balanceOf(address(this)), 69e18); - assertEq(vault.balanceOfUnderlying(address(this)), 69 ether); - - vault.approve(address(vaultETHWrapperModule), 69e19); - vaultETHWrapperModule.redeemETHFromVault(vault, 69e18); - - assertEq(address(this).balance, startingETHBal); - } - - function testFailDepositIntoNotWETHVault() public { - vaultETHWrapperModule.depositETHIntoVault{value: 1 ether}(vault); - } - - function testFailWithdrawFromNotWETHVault() public { - vault.setUnderlyingIsWETH(true); - - vaultETHWrapperModule.depositETHIntoVault{value: 1 ether}(vault); - - vault.setUnderlyingIsWETH(false); - - vault.approve(address(vaultETHWrapperModule), 1e18); - - vaultETHWrapperModule.withdrawETHFromVault(vault, 1 ether); - } - - function testFailRedeemFromNotWETHVault() public { - vault.setUnderlyingIsWETH(true); - - vaultETHWrapperModule.depositETHIntoVault{value: 1 ether}(vault); - - vault.setUnderlyingIsWETH(false); - - vault.approve(address(vaultETHWrapperModule), 1e18); - - vaultETHWrapperModule.redeemETHFromVault(vault, 1e18); - } - - receive() external payable {} -} diff --git a/src/test/VaultRouterModule.t.sol b/src/test/VaultRouterModule.t.sol new file mode 100644 index 0000000..e8889ca --- /dev/null +++ b/src/test/VaultRouterModule.t.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.9; + +import {WETH} from "solmate/tokens/WETH.sol"; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {VaultRouterModule} from "../modules/VaultRouterModule.sol"; + +import {Vault} from "../Vault.sol"; +import {VaultFactory} from "../VaultFactory.sol"; + +contract VaultRouterModuleTest is DSTestPlus { + VaultFactory vaultFactory; + + Vault wethVault; + WETH weth; + + VaultRouterModule vaultRouterModule; + + function setUp() public { + weth = new WETH(); + + wethVault = new VaultFactory().deployVault(weth); + wethVault.initialize(); + + vaultRouterModule = new VaultRouterModule(); + } + + /*/////////////////////////////////////////////////////////////// + ETH DEPOSIT/WITHDRAWAL TESTS + //////////////////////////////////////////////////////////////*/ + + function testAtomicDepositWithdrawETH() public { + wethVault.setUnderlyingIsWETH(true); + + uint256 startingETHBal = address(this).balance; + + vaultRouterModule.depositETHIntoVault{value: 1 ether}(wethVault); + + assertEq(address(this).balance, startingETHBal - 1 ether); + + assertEq(wethVault.balanceOf(address(this)), 1e18); + assertEq(wethVault.balanceOfUnderlying(address(this)), 1 ether); + + wethVault.approve(address(vaultRouterModule), 1e18); + vaultRouterModule.withdrawETHFromVault(wethVault, 1 ether); + + assertEq(address(this).balance, startingETHBal); + } + + function testAtomicDepositRedeemETH() public { + wethVault.setUnderlyingIsWETH(true); + + uint256 startingETHBal = address(this).balance; + + vaultRouterModule.depositETHIntoVault{value: 69 ether}(wethVault); + + assertEq(address(this).balance, startingETHBal - 69 ether); + + assertEq(wethVault.balanceOf(address(this)), 69e18); + assertEq(wethVault.balanceOfUnderlying(address(this)), 69 ether); + + wethVault.approve(address(vaultRouterModule), 69e19); + vaultRouterModule.redeemETHFromVault(wethVault, 69e18); + + assertEq(address(this).balance, startingETHBal); + } + + /*/////////////////////////////////////////////////////////////// + ETH DEPOSIT/WITHDRAWAL SANITY CHECK TESTS + //////////////////////////////////////////////////////////////*/ + + function testFailDepositIntoNotWETHVault() public { + vaultRouterModule.depositETHIntoVault{value: 1 ether}(wethVault); + } + + function testFailWithdrawFromNotWETHVault() public { + wethVault.setUnderlyingIsWETH(true); + + vaultRouterModule.depositETHIntoVault{value: 1 ether}(wethVault); + + wethVault.setUnderlyingIsWETH(false); + + wethVault.approve(address(vaultRouterModule), 1e18); + + vaultRouterModule.withdrawETHFromVault(wethVault, 1 ether); + } + + function testFailRedeemFromNotWETHVault() public { + wethVault.setUnderlyingIsWETH(true); + + vaultRouterModule.depositETHIntoVault{value: 1 ether}(wethVault); + + wethVault.setUnderlyingIsWETH(false); + + wethVault.approve(address(vaultRouterModule), 1e18); + + vaultRouterModule.redeemETHFromVault(wethVault, 1e18); + } + + receive() external payable {} +}