From ee8f47d0471ea5a9b7f3202a4c1491f71cd997d5 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 21 May 2024 18:43:49 +0200 Subject: [PATCH 1/5] Move ERC7575 to Last Call --- ERCS/erc-7575.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ERCS/erc-7575.md b/ERCS/erc-7575.md index 2de52a0fe8..b36dfe58dd 100644 --- a/ERCS/erc-7575.md +++ b/ERCS/erc-7575.md @@ -4,7 +4,8 @@ title: Multi-Asset ERC-4626 Vaults description: Extended ERC-4626 Interface enabling Multi-Asset Vaults author: Jeroen Offerijns (@hieronx), Alina Sinelnikova (@ilinzweilin), Vikram Arun (@vikramarun), Joey Santoro (@joeysantoro), Farhaan Ali (@0xfarhaan) discussions-to: https://ethereum-magicians.org/t/erc-7575-partial-and-extended-erc-4626-vaults/17274 -status: Review +status: Last Call +last-call-deadline: 2024-06-11 type: Standards Track category: ERC created: 2023-12-11 From b0b639c578dcc36d80c774bf62954642a6b6d5cc Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 28 May 2024 12:59:37 +0200 Subject: [PATCH 2/5] Clarifications --- ERCS/erc-7575.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ERCS/erc-7575.md b/ERCS/erc-7575.md index b36dfe58dd..013df668a2 100644 --- a/ERCS/erc-7575.md +++ b/ERCS/erc-7575.md @@ -4,8 +4,7 @@ title: Multi-Asset ERC-4626 Vaults description: Extended ERC-4626 Interface enabling Multi-Asset Vaults author: Jeroen Offerijns (@hieronx), Alina Sinelnikova (@ilinzweilin), Vikram Arun (@vikramarun), Joey Santoro (@joeysantoro), Farhaan Ali (@0xfarhaan) discussions-to: https://ethereum-magicians.org/t/erc-7575-partial-and-extended-erc-4626-vaults/17274 -status: Last Call -last-call-deadline: 2024-06-11 +status: Review type: Standards Track category: ERC created: 2023-12-11 @@ -74,7 +73,7 @@ Pipes convert between a single `asset` and `share` which are both [ERC-20](./eip A Pipe MAY be either unidirectional or bidirectional. -A unidirectional Pipe SHOULD implement only the entry function(s) `deposit` and/or `mint`. +A unidirectional Pipe SHOULD implement only the entry function(s) `deposit` and/or `mint`, not `redeem` and/or `withdraw`. ### Share-to-Vault lookup @@ -141,7 +140,7 @@ This is optional, to maintain backward compatibility with use cases where the `s ## Backwards Compatibility -[ERC-7575](./eip-7575.md) Vaults are compatible with [ERC-4626](./eip-4626.md) except for the [ERC-20](./eip-20.md) functionality which has been removed. +[ERC-7575](./eip-7575.md) Vaults are not fully compatible with [ERC-4626](./eip-4626.md) because the [ERC-20](./eip-20.md) functionality has been removed. ## Reference Implementation From 8ba1288b9a01f0b399deea1462c1da0447ea46e7 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 28 May 2024 16:14:54 +0200 Subject: [PATCH 3/5] Fix reference implementation, add pipe explanation --- ERCS/erc-7575.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ERCS/erc-7575.md b/ERCS/erc-7575.md index fa250aafab..3134600900 100644 --- a/ERCS/erc-7575.md +++ b/ERCS/erc-7575.md @@ -76,6 +76,8 @@ A Pipe MAY be either unidirectional or bidirectional. A unidirectional Pipe SHOULD implement only the entry function(s) `deposit` and/or `mint`, not `redeem` and/or `withdraw`. +The entry points SHOULD lock or burn the `asset` from the `msg.sender` and mint or transfer the `share` to the `receiver`. For bidirectional pipes, the exit points SHOULD lock or burn the `share` from the `owner` and mint or transer the `asset` to the `receiver`. + ### Share-to-Vault lookup The [ERC-20](./eip-20.md) implementation of `share` SHOULD implement a `vault` method, that returns the address of the Vault for a specific `asset`. @@ -149,16 +151,16 @@ This is optional, to maintain backward compatibility with use cases where the `s // This code snippet is incomplete pseudocode used for example only and is no way intended to be used in production or guaranteed to be secure contract Share is ERC20 { - mapping (address asset => address) vault; + mapping (address asset => address) vault; - function updateVault(address asset, address vault_) public { - vault[asset] = vault_; - emit UpdateVault(asset, vault_); + function updateVault(address asset, address vault_) public { + vault[asset] = vault_; + emit UpdateVault(asset, vault_); + } - function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { - return interfaceId == 0xf815c03d || interfaceId == 0x01ffc9a7; - } - } + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return interfaceId == 0xf815c03d || interfaceId == 0x01ffc9a7; + } } contract TokenAVault is ERC7575 { From 0c7ce188e921089a4a3a257bef87ff6b1600c439 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 28 May 2024 16:16:21 +0200 Subject: [PATCH 4/5] Fix typo --- ERCS/erc-7575.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-7575.md b/ERCS/erc-7575.md index 3134600900..efe9ec98ba 100644 --- a/ERCS/erc-7575.md +++ b/ERCS/erc-7575.md @@ -76,7 +76,7 @@ A Pipe MAY be either unidirectional or bidirectional. A unidirectional Pipe SHOULD implement only the entry function(s) `deposit` and/or `mint`, not `redeem` and/or `withdraw`. -The entry points SHOULD lock or burn the `asset` from the `msg.sender` and mint or transfer the `share` to the `receiver`. For bidirectional pipes, the exit points SHOULD lock or burn the `share` from the `owner` and mint or transer the `asset` to the `receiver`. +The entry points SHOULD lock or burn the `asset` from the `msg.sender` and mint or transfer the `share` to the `receiver`. For bidirectional pipes, the exit points SHOULD lock or burn the `share` from the `owner` and mint or transfer the `asset` to the `receiver`. ### Share-to-Vault lookup From 8fd590d6a1a7a44d4f435a96a233ad1d77e98fee Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Wed, 29 May 2024 07:14:38 +0200 Subject: [PATCH 5/5] requester => controller --- ERCS/erc-7540.md | 86 ++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/ERCS/erc-7540.md b/ERCS/erc-7540.md index ab4d1134c0..1e54476d45 100644 --- a/ERCS/erc-7540.md +++ b/ERCS/erc-7540.md @@ -41,7 +41,7 @@ The existing definitions from [ERC-4626](./eip-4626.md) apply. In addition, this - asynchronous deposit Vault: a Vault that implements asynchronous Requests for deposit flows - asynchronous redemption Vault: a Vault that implements asynchronous Requests for redemption flows - fully asynchronous Vault: a Vault that implements asynchronous Requests for both deposit and redemption flows -- requester: owner of the Request, who can manage any actions related to the Request including claiming the `assets` or `shares` +- controller: owner of the Request, who can manage any actions related to the Request including claiming the `assets` or `shares` - operator: an account that can manage Requests on behalf of another account. ### Request Flows @@ -58,7 +58,7 @@ Asynchronous deposit Vaults MUST override the ERC-4626 specification as follows: Asynchronous redeem Vaults MUST override the ERC-4626 specification as follows: 1. The `redeem` and `withdraw` methods do not transfer `shares` to the Vault, because this already happened on `requestRedeem`. -2. The `owner` field of `redeem` and `withdraw` SHOULD be renamed to `requester`, and the requester MUST be `msg.sender` unless the `requester` has approved the `msg.sender` as an operator. +2. The `owner` field of `redeem` and `withdraw` SHOULD be renamed to `controller`, and the controller MUST be `msg.sender` unless the `controller` has approved the `msg.sender` as an operator. 3. `previewRedeem` and `previewWithdraw` MUST revert for all callers and inputs. ### Request Lifecycle @@ -67,9 +67,9 @@ After submission, Requests go through Pending, Claimable, and Claimed stages. An | **State** | **User** | **Vault** | |-------------|---------------------------------|-----------| -| Pending | `requestDeposit(assets, requester, owner)` | `asset.transferFrom(owner, vault, assets)`; `pendingDepositRequest[requester] += assets` | -| Claimable | | *Internal Request fulfillment*: `pendingDepositRequest[requester] -= assets`; `claimableDepositRequest[requester] += assets` | -| Claimed | `deposit(assets, receiver)` | `claimableDepositRequest[requester] -= assets`; `vault.balanceOf[receiver] += shares` | +| Pending | `requestDeposit(assets, controller, owner)` | `asset.transferFrom(owner, vault, assets)`; `pendingDepositRequest[controller] += assets` | +| Claimable | | *Internal Request fulfillment*: `pendingDepositRequest[controller] -= assets`; `claimableDepositRequest[controller] += assets` | +| Claimed | `deposit(assets, receiver)` | `claimableDepositRequest[controller] -= assets`; `vault.balanceOf[receiver] += shares` | Note that `maxDeposit` increases and decreases in sync with `claimableDepositRequest`. @@ -90,7 +90,7 @@ If a Request becomes partially claimable, all requests of the same `requestId` M There are no assumptions or requirements of requests with different `requestId`. I.e. they MAY transition to Claimable at different times and exchange rates with no ordering or correlation enforced in any way. -When `requestId==0`, the Vault MUST use purely the `requester` to discriminate the request state. The Pending and Claimable state of multiple requests from the same `requester` would be aggregated. If a Vault returns `0` for the `requestId` of any request, it MUST return `0` for all requests. +When `requestId==0`, the Vault MUST use purely the `controller` to discriminate the request state. The Pending and Claimable state of multiple requests from the same `controller` would be aggregated. If a Vault returns `0` for the `requestId` of any request, it MUST return `0` for all requests. ### Methods @@ -98,9 +98,9 @@ When `requestId==0`, the Vault MUST use purely the `requester` to discriminate t Transfers `assets` from `owner` into the Vault and submits a Request for asynchronous `deposit`. This places the Request in Pending state, with a corresponding increase in `pendingDepositRequest` for the amount `assets`. -The output `requestId` is used to partially discriminate the request along with the `requester`. See [Request Ids](#request-ids) section for more info. +The output `requestId` is used to partially discriminate the request along with the `controller`. See [Request Ids](#request-ids) section for more info. -When the Request is Claimable, `claimableDepositRequest` will be increased for the `requester`. `deposit` or `mint` can subsequently be called by `requester` to receive `shares`. A Request MAY transition straight to Claimable state but MUST NOT skip the Claimable state. +When the Request is Claimable, `claimableDepositRequest` will be increased for the `controller`. `deposit` or `mint` can subsequently be called by `controller` to receive `shares`. A Request MAY transition straight to Claimable state but MUST NOT skip the Claimable state. The `shares` that will be received on `deposit` or `mint` MAY NOT be equivalent to the value of `convertToShares(assets)` at the time of Request, as the price can change between Request and Claim. @@ -122,7 +122,7 @@ MUST emit the `RequestDeposit` event. inputs: - name: assets type: uint256 - - name: requester + - name: controller type: address - name: owner type: address @@ -133,7 +133,7 @@ MUST emit the `RequestDeposit` event. #### pendingDepositRequest -The amount of requested `assets` in Pending state for the `requester` with the given `requestId` to `deposit` or `mint`. +The amount of requested `assets` in Pending state for the `controller` with the given `requestId` to `deposit` or `mint`. MUST NOT include any `assets` in Claimable state for `deposit` or `mint`. @@ -149,7 +149,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i inputs: - name: requestId type: uint256 - - name: requester + - name: controller type: address outputs: @@ -159,7 +159,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i #### claimableDepositRequest -The amount of requested `assets` in Claimable state for the `requester` with the given `requestId` to `deposit` or `mint`. +The amount of requested `assets` in Claimable state for the `controller` with the given `requestId` to `deposit` or `mint`. MUST NOT include any `assets` in Pending state for `deposit` or `mint`. @@ -175,7 +175,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i inputs: - name: requestId type: uint256 - - name: requester + - name: controller type: address outputs: @@ -187,7 +187,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i Assumes control of `shares` from `owner` and submits a Request for asynchronous `redeem`. This places the Request in Pending state, with a corresponding increase in `pendingRedeemRequest` for the amount `shares`. -The output `requestId` is used to partially discriminate the request along with the `requester`. See [Request Ids](#request-ids) section for more info. +The output `requestId` is used to partially discriminate the request along with the `controller`. See [Request Ids](#request-ids) section for more info. MAY support either a locking or a burning mechanism for `shares` depending on the Vault implementation. @@ -195,7 +195,7 @@ If a Vault uses a locking mechanism for `shares`, those `shares` MUST be burned MUST support a redeem Request flow where the control of `shares` is taken from `owner` directly where `msg.sender` has ERC-20 approval over the `shares` of `owner`, or the `owner` has approved the `msg.sender` as an operator. -When the Request is Claimable, `claimableRedeemRequest` will be increased for the `requester`. `redeem` or `withdraw` can subsequently be called by `requester` to receive `assets`. A Request MAY transition straight to Claimable state but MUST NOT skip the Claimable state. +When the Request is Claimable, `claimableRedeemRequest` will be increased for the `controller`. `redeem` or `withdraw` can subsequently be called by `controller` to receive `assets`. A Request MAY transition straight to Claimable state but MUST NOT skip the Claimable state. The `assets` that will be received on `redeem` or `withdraw` MAY NOT be equivalent to the value of `convertToAssets(shares)` at the time of Request, as the price can change between Pending and Claimed. @@ -211,7 +211,7 @@ MUST emit the `RequestRedeem` event. inputs: - name: shares type: uint256 - - name: requester + - name: controller type: address - name: owner type: address @@ -222,7 +222,7 @@ MUST emit the `RequestRedeem` event. #### pendingRedeemRequest -The amount of requested `shares` in Pending state for the `requester` with the given `requestId` to `redeem` or `withdraw`. +The amount of requested `shares` in Pending state for the `controller` with the given `requestId` to `redeem` or `withdraw`. MUST NOT include any `shares` in Claimable state for `redeem` or `withdraw`. @@ -238,7 +238,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i inputs: - name: requestId type: uint256 - - name: requester + - name: controller type: address outputs: @@ -248,7 +248,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i #### claimableRedeemRequest -The amount of requested `shares` in Claimable state for the `requester` with the given `requestId` to `redeem` or `withdraw`. +The amount of requested `shares` in Claimable state for the `controller` with the given `requestId` to `redeem` or `withdraw`. MUST NOT include any `shares` in Pending state for `redeem` or `withdraw`. @@ -264,7 +264,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i inputs: - name: requestId type: uint256 - - name: requester + - name: controller type: address outputs: @@ -274,7 +274,7 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i #### `isOperator` -Returns `true` if the `operator` is approved as an operator for a `requester`. +Returns `true` if the `operator` is approved as an operator for a `controller`. ```yaml - name: isOperator @@ -282,7 +282,7 @@ Returns `true` if the `operator` is approved as an operator for a `requester`. stateMutability: view inputs: - - name: requester + - name: controller type: address - name: operator type: address @@ -320,22 +320,22 @@ MUST return True. #### `deposit` and `mint` overloaded methods -Implementations MUST support an additional overloaded `deposit` and `mint` method on the specification from [ERC-4626](./eip-4626.md), with an additional `requester` input of type `address`: +Implementations MUST support an additional overloaded `deposit` and `mint` method on the specification from [ERC-4626](./eip-4626.md), with an additional `controller` input of type `address`: -- `deposit(uint256 assets, address receiver, address requester)` -- `mint(uint256 shares, address receiver, address requester)` +- `deposit(uint256 assets, address receiver, address controller)` +- `mint(uint256 shares, address receiver, address controller)` -The `requester` field is used to look up the Request for which the `assets` should be claimed. +The `controller` field is used to look up the Request for which the `assets` should be claimed. -Calls MUST revert unless `msg.sender` is either equal to `requester` or an operator approved by `requester`. +Calls MUST revert unless `msg.sender` is either equal to `controller` or an operator approved by `controller`. -When the `Deposit` event is emitted, the first parameter MUST be the `requester`, and the second parameter MUST be the `receiver`. +When the `Deposit` event is emitted, the first parameter MUST be the `controller`, and the second parameter MUST be the `receiver`. ### Events #### DepositRequest -`owner` has locked `assets` in the Vault to Request a deposit with request ID `requestId`. `requester` controls this Request. `sender` is the caller of the `requestDeposit` which may not be equal to the `owner`. +`owner` has locked `assets` in the Vault to Request a deposit with request ID `requestId`. `controller` controls this Request. `sender` is the caller of the `requestDeposit` which may not be equal to the `owner`. MUST be emitted when a deposit Request is submitted using the `requestDeposit` method. @@ -344,7 +344,7 @@ MUST be emitted when a deposit Request is submitted using the `requestDeposit` m type: event inputs: - - name: requester + - name: controller indexed: true type: address - name: owner @@ -363,7 +363,7 @@ MUST be emitted when a deposit Request is submitted using the `requestDeposit` m #### RedeemRequest -`sender` has locked `shares`, owned by `owner`, in the Vault to Request a redemption. `requester` controls this Request, but is not necessarily the `owner`. +`sender` has locked `shares`, owned by `owner`, in the Vault to Request a redemption. `controller` controls this Request, but is not necessarily the `owner`. MUST be emitted when a redemption Request is submitted using the `requestRedeem` method. @@ -372,7 +372,7 @@ MUST be emitted when a redemption Request is submitted using the `requestRedeem` type: event inputs: - - name: requester + - name: controller indexed: true type: address - name: owner @@ -391,7 +391,7 @@ MUST be emitted when a redemption Request is submitted using the `requestRedeem` #### `OperatorSet` -The `requester` has set the `approved` status to an `operator`. +The `controller` has set the `approved` status to an `operator`. MUST be logged when the operator status is set. @@ -402,7 +402,7 @@ MAY be logged when the operator status is set to the same status it was before t type: event inputs: - - name: requester + - name: controller indexed: true type: address - name: operator @@ -482,7 +482,7 @@ The state transition of a Request from Pending to Claimable happens at the Vault ### Reversion of Preview Functions in Async Request Flows -The preview functions do not take an address parameter, therefore the only way to discriminate discrepancies in the exchange rate is via the `msg.sender`. However, this could lead to integration/implementation complexities where support contracts cannot determine the output of a claim on behalf of a `requester`. +The preview functions do not take an address parameter, therefore the only way to discriminate discrepancies in the exchange rate is via the `msg.sender`. However, this could lead to integration/implementation complexities where support contracts cannot determine the output of a claim on behalf of a `controller`. In addition, there is no on-chain benefit to previewing the Claim step as the only valid state transition is to Claim anyway. If the output of a Claim is undesirable for any reason, the calling contract can revert on the output of that function call. @@ -508,9 +508,9 @@ The interface is fully backward compatible with [ERC-4626](./eip-4626.md). The s mapping(address => uint256) public claimableDepositRequest; - mapping(address requester => mapping(address operator => bool)) public isOperator; + mapping(address controller => mapping(address operator => bool)) public isOperator; - function requestDeposit(uint256 assets, address requester, address owner) external returns (uint256 requestId) { + function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId) { require(assets != 0); require(owner == msg.sender || isOperator[owner][msg.sender]); @@ -518,9 +518,9 @@ The interface is fully backward compatible with [ERC-4626](./eip-4626.md). The s asset.safeTransferFrom(owner, address(this), assets); // asset here is the Vault underlying asset - pendingDepositRequest[requester] += assets; + pendingDepositRequest[controller] += assets; - emit DepositRequest(requester, owner, requestId, msg.sender, assets); + emit DepositRequest(controller, owner, requestId, msg.sender, assets); return requestId; } @@ -528,17 +528,17 @@ The interface is fully backward compatible with [ERC-4626](./eip-4626.md). The s * Include some arbitrary transition logic here from Pending to Claimable */ - function deposit(uint256 assets, address receiver, address requester) external returns (uint256 shares) { + function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares) { require(assets != 0); - require(requester == msg.sender || isOperator[requester][msg.sender]); + require(controller == msg.sender || isOperator[controller][msg.sender]); - claimableDepositRequest[requester] -= assets; // underflow would revert if not enough claimable assets + claimableDepositRequest[controller] -= assets; // underflow would revert if not enough claimable assets shares = convertToShares(assets); // this naive example uses the instantaneous exchange rate. It may be more common to use the rate locked in upon Claimable stage. balanceOf[receiver] += shares; - emit Deposit(requester, receiver, assets, shares); + emit Deposit(controller, receiver, assets, shares); } function setOperator(address operator, bool approved) public returns (bool) {