-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ERC-7540: Asynchronous ERC-4626 Tokenized Vaults #5457
Open
DeeJayElly
wants to merge
32
commits into
OpenZeppelin:master
Choose a base branch
from
DeeJayElly:feat/ERC-7540
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+759
β0
Open
Changes from 16 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
9c8b97d
ERC-7540 - Add IERC7540 interface
DeeJayElly 6e276bd
ERC-7540 - Add ERC7540 initial implementation - requestDeposit & pendβ¦
DeeJayElly d11665c
ERC-7540 - Add ERC7540 - requestRedeem & pendingRedeemRequest/claimabβ¦
DeeJayElly 7c0d1e6
ERC-7540 - Add ERC7540 - ERC7540Fees.sol and ERC7540FeesMock.sol
DeeJayElly deb2490
ERC-7540 - Add ERC7540 - add additional Events
DeeJayElly ff2d631
ERC-7540 - Add ERC7540 - add ERC7540LimitsMock.sol
DeeJayElly 37d6252
ERC-7540 - Add ERC7540 - add ERC7540Mock.sol
DeeJayElly 6c2d9f6
ERC-7540 - Add ERC7540 - add ERC7540OffsetMock.sol
DeeJayElly d2c8762
ERC-7540 - Add ERC7540 - fix ordering
DeeJayElly 5780ff0
ERC-7540 - Add ERC7540 - remove reentrancy guard
DeeJayElly 7146c97
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly 41688b8
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly d6b1569
ERC-7540 - Add ERC7540 - fixing reentrancy
DeeJayElly 4015f6f
ERC-7540 - Add ERC7540 - add virtual to _generateRequestId
DeeJayElly e5cd1ab
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly 3a49372
ERC-7540 - Add ERC7540 - dont revert, but instead just do no-op
DeeJayElly 36eb007
ERC-7540 - Add ERC7540 - adding custom errors
DeeJayElly 31500c7
ERC-7540 - Add ERC7540 - adding custom errors
DeeJayElly dc5529f
ERC-7540 - Add ERC7540 - remove duplicated method
DeeJayElly bcb8bec
ERC-7540 - Add ERC7540 - removing events which are not under the ERC
DeeJayElly 981069c
ERC-7540 - Add ERC7540 - removing events which are not under the ERC
DeeJayElly f4092f3
ERC-7540 - Add ERC7540 - cumulative generate request id approach
DeeJayElly ff8585d
ERC-7540 - Add ERC7540 - cumulative generate request id approach - adβ¦
DeeJayElly 891b3a0
ERC-7540 - Add ERC7540 - cumulative generate request id approach - adβ¦
DeeJayElly 6135402
Merge branch 'master' into feat/ERC-7540
DeeJayElly 94ce32b
ERC-7540 - Add ERC7540 - review updates
DeeJayElly f46e0f5
ERC-7540 - Add ERC7540 - refactoring the contracts to align more withβ¦
DeeJayElly 859ab4d
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly fff03c9
Merge branch 'master' into feat/ERC-7540
DeeJayElly ef00685
ERC-7540 - Add ERC7540 - add changeset
DeeJayElly 30776ad
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly f3a616a
ERC-7540 - Add ERC7540 - add unit tests
DeeJayElly File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {IERC4626} from "../interfaces/IERC4626.sol"; | ||
|
||
/** | ||
* @dev Interface for the ERC-7540 Asynchronous Tokenized Vaults standard. | ||
* https://eips.ethereum.org/EIPS/eip-7540[ERC-7540] | ||
*/ | ||
interface IERC7540 is IERC4626 { | ||
struct Request { | ||
uint256 amount; | ||
uint256 claimable; | ||
} | ||
|
||
// Events | ||
event DepositRequest( | ||
address indexed controller, | ||
address indexed owner, | ||
uint256 indexed requestId, | ||
address sender, | ||
uint256 assets | ||
); | ||
|
||
event RedeemRequest( | ||
address indexed controller, | ||
address indexed owner, | ||
uint256 indexed requestId, | ||
address sender, | ||
uint256 shares | ||
); | ||
|
||
event OperatorSet(address indexed controller, address indexed operator, bool approved); | ||
|
||
event DepositProcessed(address indexed controller, uint256 indexed requestId, uint256 amount); | ||
|
||
event RedeemProcessed(address indexed controller, uint256 indexed requestId, uint256 amount); | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Methods | ||
|
||
/** | ||
* @dev Initiates a deposit request. | ||
* @param assets The amount of assets to deposit. | ||
* @param controller The address of the controller managing the request. | ||
* @param owner The owner of the assets. | ||
* @return requestId The unique identifier for this deposit request. | ||
*/ | ||
function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); | ||
|
||
/** | ||
* @dev Initiates a redeem request. | ||
* @param shares The amount of shares to redeem. | ||
* @param controller The address of the controller managing the request. | ||
* @param owner The owner of the shares. | ||
* @return requestId The unique identifier for this redeem request. | ||
*/ | ||
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); | ||
|
||
/** | ||
* @dev Gets the pending deposit request amount for a given controller and requestId. | ||
* @param requestId The unique identifier for the request. | ||
* @param controller The address of the controller. | ||
* @return assets The amount of assets in the pending state. | ||
*/ | ||
function pendingDepositRequest(uint256 requestId, address controller) external view returns (uint256 assets); | ||
|
||
/** | ||
* @dev Gets the pending redeem request amount for a given controller and requestId. | ||
* @param requestId The unique identifier for the request. | ||
* @param controller The address of the controller. | ||
* @return shares The amount of shares in the pending state. | ||
*/ | ||
function pendingRedeemRequest(uint256 requestId, address controller) external view returns (uint256 shares); | ||
|
||
/** | ||
* @dev Gets the claimable deposit request amount for a given controller and requestId. | ||
* @param requestId The unique identifier for the request. | ||
* @param controller The address of the controller. | ||
* @return assets The amount of assets in the claimable state. | ||
*/ | ||
function claimableDepositRequest(uint256 requestId, address controller) external view returns (uint256 assets); | ||
|
||
/** | ||
* @dev Gets the claimable redeem request amount for a given controller and requestId. | ||
* @param requestId The unique identifier for the request. | ||
* @param controller The address of the controller. | ||
* @return shares The amount of shares in the claimable state. | ||
*/ | ||
function claimableRedeemRequest(uint256 requestId, address controller) external view returns (uint256 shares); | ||
|
||
/** | ||
* @dev Sets or revokes an operator for the given controller. | ||
* @param operator The address of the operator. | ||
* @param approved The approval status of the operator. | ||
* @return success Whether the operation was successful. | ||
*/ | ||
function setOperator(address operator, bool approved) external returns (bool success); | ||
|
||
/** | ||
* @dev Checks if an operator is approved for a controller. | ||
* @param controller The address of the controller. | ||
* @param operator The address of the operator. | ||
* @return status Whether the operator is approved. | ||
*/ | ||
function isOperator(address controller, address operator) external view returns (bool status); | ||
|
||
/** | ||
* @dev Gets the details of a pending deposit request. | ||
* @param controller The address of the controller. | ||
* @param requestId The unique identifier for the deposit request. | ||
* @return request The request object containing amount and claimable details. | ||
*/ | ||
function getPendingDepositRequest( | ||
address controller, | ||
uint256 requestId | ||
) external view returns (Request memory request); | ||
|
||
/** | ||
* @dev Gets the details of a pending redeem request. | ||
* @param controller The address of the controller. | ||
* @param requestId The unique identifier for the redeem request. | ||
* @return request The request object containing amount and claimable details. | ||
*/ | ||
function getPendingRedeemRequest( | ||
address controller, | ||
uint256 requestId | ||
) external view returns (Request memory request); | ||
DeeJayElly marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* @dev Gets the status of an operator for a given controller. | ||
* @param controller The address of the controller. | ||
* @param operator The address of the operator. | ||
* @return status Whether the operator is approved. | ||
*/ | ||
function getOperator(address controller, address operator) external view returns (bool status); | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {IERC4626} from "../../interfaces/IERC4626.sol"; | ||
import {IERC20} from "../../token/ERC20/IERC20.sol"; | ||
import {ERC7540} from "../../token/ERC20/extensions/ERC7540.sol"; | ||
import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; | ||
import {SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol"; | ||
import {Math} from "../../utils/math/Math.sol"; | ||
|
||
/** | ||
* @dev ERC-7540 vault with entry/exit fees expressed in basis points. | ||
* | ||
* NOTE: This contract charges fees in terms of assets, not shares. The fees are calculated | ||
* based on the amount of assets being deposited or withdrawn, not the shares being minted or redeemed. | ||
* This is an opinionated design decision that should be taken into account when integrating. | ||
* | ||
* WARNING: This contract is for demonstration purposes and has not been audited. Use it with caution. | ||
*/ | ||
abstract contract ERC7540Fees is ERC7540 { | ||
using SafeERC20 for IERC20; | ||
using Math for uint256; | ||
|
||
uint256 private constant _BASIS_POINT_SCALE = 1e4; | ||
|
||
function previewDeposit(uint256 assets) public view virtual override(ERC4626, IERC4626) returns (uint256) { | ||
uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); | ||
return super.previewDeposit(assets - fee); | ||
} | ||
|
||
function previewRedeem(uint256 shares) public view virtual override(ERC4626, IERC4626) returns (uint256) { | ||
uint256 assets = super.previewRedeem(shares); | ||
uint256 fee = _feeOnTotal(assets, _exitFeeBasisPoints()); | ||
return assets - fee; | ||
} | ||
|
||
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override { | ||
uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); | ||
address recipient = _entryFeeRecipient(); | ||
|
||
super._deposit(caller, receiver, assets - fee, shares); | ||
|
||
if (fee > 0 && recipient != address(this)) { | ||
IERC20(asset()).safeTransfer(recipient, fee); | ||
} | ||
} | ||
|
||
function _withdraw( | ||
address caller, | ||
address receiver, | ||
address owner, | ||
uint256 assets, | ||
uint256 shares | ||
) internal virtual override { | ||
uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); | ||
address recipient = _exitFeeRecipient(); | ||
|
||
super._withdraw(caller, receiver, owner, assets - fee, shares); | ||
|
||
if (fee > 0 && recipient != address(this)) { | ||
IERC20(asset()).safeTransfer(recipient, fee); | ||
} | ||
} | ||
|
||
function _entryFeeBasisPoints() internal view virtual returns (uint256) { | ||
return 0; // replace with e.g. 100 for 1% | ||
} | ||
|
||
function _exitFeeBasisPoints() internal view virtual returns (uint256) { | ||
return 0; // replace with e.g. 100 for 1% | ||
} | ||
|
||
function _entryFeeRecipient() internal view virtual returns (address) { | ||
return address(0); // replace with e.g. a treasury address | ||
} | ||
|
||
function _exitFeeRecipient() internal view virtual returns (address) { | ||
return address(0); // replace with e.g. a treasury address | ||
} | ||
|
||
function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { | ||
return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil); | ||
} | ||
|
||
function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { | ||
return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {ERC7540Fees} from "../docs/ERC7540Fees.sol"; | ||
import {ERC7540} from "../../token/ERC20/extensions/ERC7540.sol"; | ||
import {IERC20} from "../../token/ERC20/IERC20.sol"; | ||
|
||
abstract contract ERC7540FeesMock is ERC7540Fees { | ||
uint256 private immutable _entryFeeBasisPointValue; | ||
address private immutable _entryFeeRecipientValue; | ||
uint256 private immutable _exitFeeBasisPointValue; | ||
address private immutable _exitFeeRecipientValue; | ||
|
||
constructor( | ||
IERC20 asset, | ||
uint256 entryFeeBasisPoints, | ||
address entryFeeRecipient, | ||
uint256 exitFeeBasisPoints, | ||
address exitFeeRecipient | ||
) ERC7540(asset) { | ||
_entryFeeBasisPointValue = entryFeeBasisPoints; | ||
_entryFeeRecipientValue = entryFeeRecipient; | ||
_exitFeeBasisPointValue = exitFeeBasisPoints; | ||
_exitFeeRecipientValue = exitFeeRecipient; | ||
} | ||
|
||
function _entryFeeBasisPoints() internal view virtual override returns (uint256) { | ||
return _entryFeeBasisPointValue; | ||
} | ||
|
||
function _entryFeeRecipient() internal view virtual override returns (address) { | ||
return _entryFeeRecipientValue; | ||
} | ||
|
||
function _exitFeeBasisPoints() internal view virtual override returns (uint256) { | ||
return _exitFeeBasisPointValue; | ||
} | ||
|
||
function _exitFeeRecipient() internal view virtual override returns (address) { | ||
return _exitFeeRecipientValue; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {IERC20} from "../../token/ERC20/IERC20.sol"; | ||
import {IERC4626} from "../../interfaces/IERC4626.sol"; | ||
import {ERC7540} from "../../token/ERC20/extensions/ERC7540.sol"; | ||
import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; | ||
|
||
abstract contract ERC7540LimitsMock is ERC7540 { | ||
uint256 private immutable _maxDeposit; | ||
uint256 private immutable _maxMint; | ||
|
||
constructor(IERC20 asset, uint256 maxDepositValue, uint256 maxMintValue) ERC7540(asset) { | ||
_maxDeposit = maxDepositValue; | ||
_maxMint = maxMintValue; | ||
} | ||
|
||
function maxDeposit(address) public view override(IERC4626, ERC4626) returns (uint256) { | ||
return _maxDeposit; | ||
} | ||
|
||
function maxMint(address) public view override(IERC4626, ERC4626) returns (uint256) { | ||
return _maxMint; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {IERC20} from "../../token/ERC20/IERC20.sol"; | ||
import {ERC20} from "../../token/ERC20/ERC20.sol"; | ||
import {ERC7540} from "../../token/ERC20/extensions/ERC7540.sol"; | ||
import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; | ||
|
||
contract ERC7540Mock is ERC7540 { | ||
constructor(IERC20 asset) ERC20("ERC4626Mock", "E4626M") ERC7540(asset) {} | ||
|
||
function processPendingDeposit(uint256 requestId, address controller) external view { | ||
Request memory request = getPendingDepositRequest(controller, requestId); | ||
require(request.amount > 0, "No pending deposit request"); | ||
|
||
request.claimable += request.amount; | ||
request.amount = 0; | ||
} | ||
|
||
function processPendingRedeem(uint256 requestId, address controller) external view { | ||
Request memory request = getPendingRedeemRequest(controller, requestId); | ||
require(request.amount > 0, "No pending redeem request"); | ||
|
||
request.claimable += request.amount; | ||
request.amount = 0; | ||
} | ||
|
||
function _processPendingRequests(uint256 requestId, address controller) internal override { | ||
Request memory depositRequest = getPendingDepositRequest(controller, requestId); | ||
if (depositRequest.amount > 0) { | ||
depositRequest.claimable += depositRequest.amount; | ||
emit DepositProcessed(controller, requestId, depositRequest.amount); | ||
depositRequest.amount = 0; | ||
} | ||
|
||
Request memory redeemRequest = getPendingRedeemRequest(controller, requestId); | ||
if (redeemRequest.amount > 0) { | ||
redeemRequest.claimable += redeemRequest.amount; | ||
emit RedeemProcessed(controller, requestId, redeemRequest.amount); | ||
redeemRequest.amount = 0; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {IERC20} from "../../token/ERC20/IERC20.sol"; | ||
import {ERC7540} from "../../token/ERC20/extensions/ERC7540.sol"; | ||
|
||
abstract contract ERC7540OffsetMock is ERC7540 { | ||
uint8 private immutable _offset; | ||
|
||
constructor(IERC20 asset, uint8 offset) ERC7540(asset) { | ||
_offset = offset; | ||
} | ||
|
||
function _decimalsOffset() internal view override returns (uint8) { | ||
return _offset; | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not part of the ERC