From 6240b7584c011cf5c53f71987436ab2c112746cd Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Wed, 21 Aug 2024 12:14:12 +0200 Subject: [PATCH] Compiling fully async vault --- src/ControlledAsyncDeposits.sol | 29 ++++--- src/FullyAsyncVault.sol | 128 +++++++++++++++++++++++++++++ src/TimelockedAsyncWithdrawals.sol | 36 +++++--- 3 files changed, 169 insertions(+), 24 deletions(-) diff --git a/src/ControlledAsyncDeposits.sol b/src/ControlledAsyncDeposits.sol index 0a6f4e4..2753385 100644 --- a/src/ControlledAsyncDeposits.sol +++ b/src/ControlledAsyncDeposits.sol @@ -19,7 +19,7 @@ import {ERC20} from "solmate/tokens/ERC20.sol"; * Conversions between claimable assets/shares should be checked for rounding safety. */ abstract contract BaseControlledAsyncDeposits is BaseERC7540, IERC7540Deposit { - uint256 internal _totalPendingAssets; + uint256 internal _totalPendingDepositAssets; mapping(address => PendingDeposit) internal _pendingDeposit; mapping(address => ClaimableDeposit) internal _claimableDeposit; @@ -32,10 +32,10 @@ abstract contract BaseControlledAsyncDeposits is BaseERC7540, IERC7540Deposit { uint256 shares; } - function totalAssets() public view override returns (uint256) { + function totalAssets() public view virtual override returns (uint256) { // total assets pending redemption must be removed from the reported total assets // otherwise pending assets would be treated as yield for outstanding shares - return ERC20(asset).balanceOf(address(this)) - _totalPendingAssets; + return ERC20(asset).balanceOf(address(this)) - _totalPendingDepositAssets; } /*////////////////////////////////////////////////////////////// @@ -53,7 +53,7 @@ abstract contract BaseControlledAsyncDeposits is BaseERC7540, IERC7540Deposit { uint256 currentPendingAssets = _pendingDeposit[controller].assets; _pendingDeposit[controller] = PendingDeposit(assets + currentPendingAssets); - _totalPendingAssets += assets; + _totalPendingDepositAssets += assets; emit DepositRequest(controller, owner, REQUEST_ID, msg.sender, assets); return REQUEST_ID; @@ -84,7 +84,7 @@ abstract contract BaseControlledAsyncDeposits is BaseERC7540, IERC7540Deposit { ClaimableDeposit(request.assets + currentClaimableAssets, shares + currentClaimableShares); delete _pendingDeposit[controller]; - _totalPendingAssets -= request.assets; + _totalPendingDepositAssets -= request.assets; } /*////////////////////////////////////////////////////////////// @@ -103,6 +103,10 @@ abstract contract BaseControlledAsyncDeposits is BaseERC7540, IERC7540Deposit { emit Deposit(receiver, controller, assets, shares); } + function deposit(uint256 assets, address receiver) public virtual override returns (uint256 shares) { + shares = deposit(assets, receiver, receiver); + } + function mint(uint256 shares, address receiver, address controller) public override returns (uint256 assets) { require(controller == msg.sender || isOperator[controller][msg.sender], "ERC7540Vault/invalid-caller"); require(shares != 0 && shares == maxMint(controller), "Must claim nonzero maximum"); @@ -115,20 +119,23 @@ abstract contract BaseControlledAsyncDeposits is BaseERC7540, IERC7540Deposit { emit Deposit(receiver, controller, assets, shares); } - function maxDeposit(address controller) public view override returns (uint256) { + function mint(uint256 shares, address receiver) public virtual override returns (uint256 assets) { + assets = mint(shares, receiver, receiver); + } + + function maxDeposit(address controller) public view virtual override returns (uint256) { return _claimableDeposit[controller].assets; } - function maxMint(address controller) public view override returns (uint256) { + function maxMint(address controller) public view virtual override returns (uint256) { return _claimableDeposit[controller].shares; } - // preview functions always revert for async flows - function previewDeposit(uint256) public pure override returns (uint256) { + function previewDeposit(uint256) public pure virtual override returns (uint256) { revert("ERC7540Vault/async-flow"); } - function previewMint(uint256) public pure override returns (uint256) { + function previewMint(uint256) public pure virtual override returns (uint256) { revert("ERC7540Vault/async-flow"); } @@ -136,7 +143,7 @@ abstract contract BaseControlledAsyncDeposits is BaseERC7540, IERC7540Deposit { ERC165 LOGIC //////////////////////////////////////////////////////////////*/ - function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) { return interfaceId == type(IERC7540Deposit).interfaceId || super.supportsInterface(interfaceId); } } diff --git a/src/FullyAsyncVault.sol b/src/FullyAsyncVault.sol index 34f34ef..045739c 100644 --- a/src/FullyAsyncVault.sol +++ b/src/FullyAsyncVault.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; +import {IERC7540Deposit, IERC7540Redeem} from "src/interfaces/IERC7540.sol"; import {BaseERC7540} from "src/BaseERC7540.sol"; +import {ERC4626} from "solmate/mixins/ERC4626.sol"; import {BaseControlledAsyncDeposits} from "src/ControlledAsyncDeposits.sol"; import {BaseTimelockedAsyncWithdrawals} from "src/TimelockedAsyncWithdrawals.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; @@ -12,4 +14,130 @@ contract FullyAsyncVault is BaseControlledAsyncDeposits, BaseTimelockedAsyncWith BaseTimelockedAsyncWithdrawals() BaseERC7540(_asset, _name, _symbol) {} + + function totalAssets() + public + view + override(BaseControlledAsyncDeposits, BaseTimelockedAsyncWithdrawals) + returns (uint256) + { + return ERC20(asset).balanceOf(address(this)) - _totalPendingDepositAssets - _totalPendingRedeemAssets; + } + + function maxDeposit(address controller) + public + view + override(BaseControlledAsyncDeposits, ERC4626) + returns (uint256) + { + return BaseControlledAsyncDeposits.maxDeposit(controller); + } + + function previewDeposit(uint256) + public + pure + virtual + override(BaseControlledAsyncDeposits, ERC4626) + returns (uint256) + { + revert("ERC7540Vault/async-flow"); + } + + function deposit(uint256 assets, address receiver) + public + virtual + override(BaseControlledAsyncDeposits, ERC4626) + returns (uint256 shares) + { + shares = BaseControlledAsyncDeposits.deposit(assets, receiver, receiver); + } + + function maxMint(address controller) public view override(BaseControlledAsyncDeposits, ERC4626) returns (uint256) { + return BaseControlledAsyncDeposits.maxMint(controller); + } + + function previewMint(uint256) + public + pure + virtual + override(BaseControlledAsyncDeposits, ERC4626) + returns (uint256) + { + revert("ERC7540Vault/async-flow"); + } + + function mint(uint256 shares, address receiver) + public + virtual + override(BaseControlledAsyncDeposits, ERC4626) + returns (uint256 assets) + { + assets = BaseControlledAsyncDeposits.mint(shares, receiver, receiver); + } + + function maxWithdraw(address controller) + public + view + override(BaseTimelockedAsyncWithdrawals, ERC4626) + returns (uint256) + { + return BaseTimelockedAsyncWithdrawals.maxWithdraw(controller); + } + + function previewWithdraw(uint256) + public + pure + virtual + override(BaseTimelockedAsyncWithdrawals, ERC4626) + returns (uint256) + { + revert("ERC7540Vault/async-flow"); + } + + function withdraw(uint256 assets, address receiver, address controller) + public + virtual + override(BaseTimelockedAsyncWithdrawals, ERC4626) + returns (uint256 shares) + { + shares = BaseTimelockedAsyncWithdrawals.withdraw(assets, receiver, controller); + } + + function maxRedeem(address controller) + public + view + override(BaseTimelockedAsyncWithdrawals, ERC4626) + returns (uint256) + { + return BaseTimelockedAsyncWithdrawals.maxRedeem(controller); + } + + function previewRedeem(uint256) + public + pure + virtual + override(BaseTimelockedAsyncWithdrawals, ERC4626) + returns (uint256) + { + revert("ERC7540Vault/async-flow"); + } + + function redeem(uint256 shares, address receiver, address controller) + public + virtual + override(BaseTimelockedAsyncWithdrawals, ERC4626) + returns (uint256 assets) + { + assets = BaseTimelockedAsyncWithdrawals.redeem(shares, receiver, controller); + } + + function supportsInterface(bytes4 interfaceId) + public + pure + override(BaseControlledAsyncDeposits, BaseTimelockedAsyncWithdrawals) + returns (bool) + { + return interfaceId == type(IERC7540Deposit).interfaceId || interfaceId == type(IERC7540Redeem).interfaceId + || super.supportsInterface(interfaceId); + } } diff --git a/src/TimelockedAsyncWithdrawals.sol b/src/TimelockedAsyncWithdrawals.sol index 5849069..a73f93e 100644 --- a/src/TimelockedAsyncWithdrawals.sol +++ b/src/TimelockedAsyncWithdrawals.sol @@ -24,7 +24,7 @@ import {ERC20} from "solmate/tokens/ERC20.sol"; abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem { uint32 public constant TIMELOCK = 3 days; - uint256 internal _totalPendingApproxAssets; + uint256 internal _totalPendingRedeemAssets; mapping(address => RedemptionRequest) internal _pendingRedemption; struct RedemptionRequest { @@ -33,8 +33,8 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem uint32 claimableTimestamp; } - function totalAssets() public view override returns (uint256) { - return ERC20(asset).balanceOf(address(this)) - _totalPendingApproxAssets; + function totalAssets() public view virtual override returns (uint256) { + return ERC20(asset).balanceOf(address(this)) - _totalPendingRedeemAssets; } /*////////////////////////////////////////////////////////////// @@ -53,7 +53,7 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem _pendingRedemption[controller] = RedemptionRequest({assets: assets, shares: shares, claimableTimestamp: uint32(block.timestamp) + TIMELOCK}); - _totalPendingApproxAssets += assets; + _totalPendingRedeemAssets += assets; emit RedeemRequest(controller, owner, REQUEST_ID, msg.sender, shares); return REQUEST_ID; @@ -79,7 +79,12 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem ERC4626 OVERRIDDEN LOGIC //////////////////////////////////////////////////////////////*/ - function withdraw(uint256 assets, address receiver, address controller) public override returns (uint256 shares) { + function withdraw(uint256 assets, address receiver, address controller) + public + virtual + override + returns (uint256 shares) + { require(controller == msg.sender || isOperator[controller][msg.sender], "ERC7540Vault/invalid-caller"); require(assets != 0 && assets == maxWithdraw(controller), "Must claim nonzero maximum"); @@ -90,14 +95,19 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem uint256 claimableAssets = request.assets; delete _pendingRedemption[controller]; - _totalPendingApproxAssets -= claimableAssets; + _totalPendingRedeemAssets -= claimableAssets; SafeTransferLib.safeTransfer(asset, receiver, claimableAssets); emit Withdraw(msg.sender, receiver, controller, claimableAssets, shares); } - function redeem(uint256 shares, address receiver, address controller) public override returns (uint256 assets) { + function redeem(uint256 shares, address receiver, address controller) + public + virtual + override + returns (uint256 assets) + { require(controller == msg.sender || isOperator[controller][msg.sender], "ERC7540Vault/invalid-caller"); require(shares != 0 && shares == maxRedeem(controller), "Must claim nonzero maximum"); @@ -107,14 +117,14 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem assets = request.assets; delete _pendingRedemption[controller]; - _totalPendingApproxAssets -= assets; + _totalPendingRedeemAssets -= assets; SafeTransferLib.safeTransfer(asset, receiver, assets); emit Withdraw(msg.sender, receiver, controller, assets, shares); } - function maxWithdraw(address controller) public view override returns (uint256) { + function maxWithdraw(address controller) public view virtual override returns (uint256) { RedemptionRequest memory request = _pendingRedemption[controller]; if (request.claimableTimestamp <= block.timestamp) { return request.assets; @@ -122,7 +132,7 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem return 0; } - function maxRedeem(address controller) public view override returns (uint256) { + function maxRedeem(address controller) public view virtual override returns (uint256) { RedemptionRequest memory request = _pendingRedemption[controller]; if (request.claimableTimestamp <= block.timestamp) { return request.shares; @@ -131,11 +141,11 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem } // Preview functions always revert for async flows - function previewWithdraw(uint256) public pure override returns (uint256) { + function previewWithdraw(uint256) public pure virtual override returns (uint256) { revert("ERC7540Vault/async-flow"); } - function previewRedeem(uint256) public pure override returns (uint256) { + function previewRedeem(uint256) public pure virtual override returns (uint256) { revert("ERC7540Vault/async-flow"); } @@ -143,7 +153,7 @@ abstract contract BaseTimelockedAsyncWithdrawals is BaseERC7540, IERC7540Redeem ERC165 LOGIC //////////////////////////////////////////////////////////////*/ - function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) { return interfaceId == type(IERC7540Redeem).interfaceId || super.supportsInterface(interfaceId); } }