Skip to content
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
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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 Jan 24, 2025
6e276bd
ERC-7540 - Add ERC7540 initial implementation - requestDeposit & pend…
DeeJayElly Jan 24, 2025
d11665c
ERC-7540 - Add ERC7540 - requestRedeem & pendingRedeemRequest/claimab…
DeeJayElly Jan 24, 2025
7c0d1e6
ERC-7540 - Add ERC7540 - ERC7540Fees.sol and ERC7540FeesMock.sol
DeeJayElly Jan 26, 2025
deb2490
ERC-7540 - Add ERC7540 - add additional Events
DeeJayElly Jan 26, 2025
ff2d631
ERC-7540 - Add ERC7540 - add ERC7540LimitsMock.sol
DeeJayElly Jan 26, 2025
37d6252
ERC-7540 - Add ERC7540 - add ERC7540Mock.sol
DeeJayElly Jan 26, 2025
6c2d9f6
ERC-7540 - Add ERC7540 - add ERC7540OffsetMock.sol
DeeJayElly Jan 26, 2025
d2c8762
ERC-7540 - Add ERC7540 - fix ordering
DeeJayElly Jan 28, 2025
5780ff0
ERC-7540 - Add ERC7540 - remove reentrancy guard
DeeJayElly Jan 28, 2025
7146c97
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly Jan 28, 2025
41688b8
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly Jan 28, 2025
d6b1569
ERC-7540 - Add ERC7540 - fixing reentrancy
DeeJayElly Jan 28, 2025
4015f6f
ERC-7540 - Add ERC7540 - add virtual to _generateRequestId
DeeJayElly Jan 28, 2025
e5cd1ab
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly Jan 28, 2025
3a49372
ERC-7540 - Add ERC7540 - dont revert, but instead just do no-op
DeeJayElly Jan 28, 2025
36eb007
ERC-7540 - Add ERC7540 - adding custom errors
DeeJayElly Jan 28, 2025
31500c7
ERC-7540 - Add ERC7540 - adding custom errors
DeeJayElly Jan 28, 2025
dc5529f
ERC-7540 - Add ERC7540 - remove duplicated method
DeeJayElly Jan 28, 2025
bcb8bec
ERC-7540 - Add ERC7540 - removing events which are not under the ERC
DeeJayElly Jan 28, 2025
981069c
ERC-7540 - Add ERC7540 - removing events which are not under the ERC
DeeJayElly Jan 28, 2025
f4092f3
ERC-7540 - Add ERC7540 - cumulative generate request id approach
DeeJayElly Jan 28, 2025
ff8585d
ERC-7540 - Add ERC7540 - cumulative generate request id approach - ad…
DeeJayElly Jan 28, 2025
891b3a0
ERC-7540 - Add ERC7540 - cumulative generate request id approach - ad…
DeeJayElly Jan 28, 2025
6135402
Merge branch 'master' into feat/ERC-7540
DeeJayElly Jan 28, 2025
94ce32b
ERC-7540 - Add ERC7540 - review updates
DeeJayElly Jan 30, 2025
f46e0f5
ERC-7540 - Add ERC7540 - refactoring the contracts to align more with…
DeeJayElly Jan 30, 2025
859ab4d
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly Jan 30, 2025
fff03c9
Merge branch 'master' into feat/ERC-7540
DeeJayElly Jan 30, 2025
ef00685
ERC-7540 - Add ERC7540 - add changeset
DeeJayElly Jan 31, 2025
30776ad
ERC-7540 - Add ERC7540 - fixing state variables and methods visibility
DeeJayElly Jan 31, 2025
f3a616a
ERC-7540 - Add ERC7540 - add unit tests
DeeJayElly Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions contracts/interfaces/IERC7540.sol
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 {
Copy link
Collaborator

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

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
}
89 changes: 89 additions & 0 deletions contracts/mocks/docs/ERC7540Fees.sol
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);
}
}
43 changes: 43 additions & 0 deletions contracts/mocks/token/ERC7540FeesMock.sol
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;
}
}
26 changes: 26 additions & 0 deletions contracts/mocks/token/ERC7540LimitsMock.sol
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;
}
}
44 changes: 44 additions & 0 deletions contracts/mocks/token/ERC7540Mock.sol
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;
}
}
}
18 changes: 18 additions & 0 deletions contracts/mocks/token/ERC7540OffsetMock.sol
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;
}
}
Loading
Loading