Skip to content

Commit

Permalink
ERC-7540 - Add ERC7540 - refactoring the contracts to align more with…
Browse files Browse the repository at this point in the history
… the standard
  • Loading branch information
DeeJayElly committed Jan 30, 2025
1 parent 94ce32b commit f46e0f5
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 29 deletions.
11 changes: 9 additions & 2 deletions contracts/interfaces/IERC7540.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,26 @@ interface IERC7540 is IERC4626 {
event OperatorSet(address indexed controller, address indexed operator, bool approved);

/**
* @dev Indicates an error related to the current `shares` of a `sender`. Used in transfers.
* @dev Indicates an error related to the current `shares` of a `sender`.
* @param sender Address whose tokens are being transferred.
* @param shares Current shares for the interacting account.
*/
error ERC7540ZeroSharesNotAllowed(address sender, uint256 shares);

/**
* @dev Indicates an error related to the current `assets` of a `sender`. Used in transfers.
* @dev Indicates an error related to the current `assets` of a `sender`.
* @param sender Address whose tokens are being transferred.
* @param owner Address of the owner.
*/
error ERC7540Unauthorized(address sender, address owner);

/**
* @dev Indicates an error related to the current insufficient `claimable amount` of a `sender`.
* @param shares Current shares for the interacting account.
* @param amount Amount to be claimed.
*/
error ERC7540InsufficientClaimable(uint256 shares, uint256 amount);

// Methods

/**
Expand Down
19 changes: 10 additions & 9 deletions contracts/mocks/docs/ERC7540Fees.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.20;

import {IERC4626} from "../../interfaces/IERC4626.sol";
import {IERC7540} from "../../interfaces/IERC7540.sol";
import {IERC20} from "../../token/ERC20/IERC20.sol";
import {ERC7540} from "../../token/ERC20/extensions/ERC7540.sol";
import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol";
Expand All @@ -24,16 +25,16 @@ abstract contract ERC7540Fees is ERC7540 {

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 previewDeposit(uint256 assets) public view virtual override(ERC7540) 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 previewRedeem(uint256 shares) public view virtual override(ERC7540) 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());
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/token/ERC7540LimitsMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ abstract contract ERC7540LimitsMock is ERC7540 {
_maxMint = maxMintValue;
}

function maxDeposit(address) public view override(IERC4626, ERC4626) returns (uint256) {
function maxDeposit(address) public view override(ERC7540) returns (uint256) {
return _maxDeposit;
}

function maxMint(address) public view override(IERC4626, ERC4626) returns (uint256) {
function maxMint(address) public view override(ERC4626, IERC4626) returns (uint256) {
return _maxMint;
}
}
126 changes: 110 additions & 16 deletions contracts/token/ERC20/extensions/ERC7540.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import {IERC7540} from "../../../interfaces/IERC7540.sol";
import {IERC4626} from "../../../interfaces/IERC4626.sol";
import {IERC20} from "../../../interfaces/IERC20.sol";
import {ERC4626} from "./ERC4626.sol";
import {IERC165} from "../../../interfaces/IERC165.sol";
import {SafeERC20} from "../utils/SafeERC20.sol";
import {Math} from "../../../utils/math/Math.sol";

/**
* @dev Abstract implementation of the ERC-7540 standard, extending ERC-4626.
*/
abstract contract ERC7540 is ERC4626, IERC7540 {
abstract contract ERC7540 is ERC4626, IERC7540, IERC165 {
using SafeERC20 for IERC20;
using Math for uint256;

Expand All @@ -36,7 +37,7 @@ abstract contract ERC7540 is ERC4626, IERC7540 {
uint256 assets,
address controller,
address owner
) external override returns (uint256 requestId) {
) public virtual override returns (uint256 requestId) {
address sender = _msgSender();

if (assets == 0) {
Expand All @@ -49,9 +50,7 @@ abstract contract ERC7540 is ERC4626, IERC7540 {

requestId = _generateRequestId(controller, assets);

uint256 shares = previewDeposit(assets);

_pendingDepositRequests[controller][requestId].amount += shares;
_pendingDepositRequests[controller][requestId].amount += assets;

IERC20(asset()).safeTransferFrom(owner, address(this), assets);

Expand All @@ -65,7 +64,7 @@ abstract contract ERC7540 is ERC4626, IERC7540 {
uint256 shares,
address controller,
address owner
) external override returns (uint256 requestId) {
) public virtual override returns (uint256 requestId) {
address sender = _msgSender();

if (shares == 0) {
Expand All @@ -78,18 +77,30 @@ abstract contract ERC7540 is ERC4626, IERC7540 {

requestId = _generateRequestId(controller, shares);

uint256 assets = previewRedeem(shares);

_burn(owner, shares);

_pendingRedeemRequests[controller][requestId].amount += assets;
_pendingRedeemRequests[controller][requestId].amount += shares;

// Set vesting unlock time
_vestingTimestamps[requestId] = block.timestamp + VESTING_DURATION;

emit RedeemRequest(controller, owner, requestId, sender, shares);
}

/*
* @dev Overrides maxDeposit to allow deposits up to claimable request amount
*/
function maxDeposit(address controller) public view virtual override(ERC4626, IERC4626) returns (uint256) {
return _pendingDepositRequests[controller][0].claimable;
}

/*
* @dev Overrides maxRedeem to allow redemptions up to claimable request amount
*/
function maxRedeem(address controller) public view virtual override(ERC4626, IERC4626) returns (uint256) {
return _pendingRedeemRequests[controller][0].claimable;
}

/**
* @dev Gets the pending deposit request amount.
*/
Expand Down Expand Up @@ -118,14 +129,57 @@ abstract contract ERC7540 is ERC4626, IERC7540 {
return _pendingRedeemRequests[controller][requestId].claimable;
}

/** @dev See {IERC4626-maxWithdraw}. */
function maxWithdraw(address) public pure override(ERC4626, IERC4626) returns (uint256) {
return 0; // Withdrawals are only possible through requestRedeem
/**
* @dev Implements ERC-7540 deposit by allowing users to deposit through calling ERC4626 deposit.
*/
function deposit(uint256 assets, address receiver, address controller) public virtual returns (uint256 shares) {
address sender = _msgSender();

if (sender != controller && !isOperator(controller, sender)) {
revert ERC7540Unauthorized(sender, controller);
}

Request storage request = _pendingDepositRequests[controller][0];
if (request.claimable < assets) {
revert ERC7540InsufficientClaimable(assets, request.claimable);
}

request.claimable -= assets;

uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) {
revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
}

shares = super.deposit(assets, receiver);

return shares;
}

/** @dev See {IERC4626-maxRedeem}. */
function maxRedeem(address) public pure override(ERC4626, IERC4626) returns (uint256) {
return 0; // Redemptions are only possible through requestRedeem
/**
* @dev Implements ERC-7540 claim by allowing users to redeem claimable shares.
*/
function redeem(uint256 shares, address receiver, address controller) public virtual override(ERC4626, IERC4626) returns (uint256 assets) {
address sender = _msgSender();
if (sender != controller && !isOperator(controller, sender)) {
revert ERC7540Unauthorized(sender, controller);
}

Request storage request = _pendingRedeemRequests[controller][0];
if (request.claimable < shares) {
revert ERC7540InsufficientClaimable(shares, request.claimable);
}

request.claimable -= shares;

uint256 maxShares = maxRedeem(controller);
if (shares > maxShares) {
revert ERC4626ExceededMaxRedeem(controller, shares, maxShares);
}

assets = super.redeem(shares, receiver, controller);

return assets;
}

/**
Expand All @@ -145,6 +199,34 @@ abstract contract ERC7540 is ERC4626, IERC7540 {
return _operators[controller][operator];
}

function previewDeposit(uint256) public view virtual override(ERC4626, IERC4626) returns (uint256) {
revert("ERC7540: previewDeposit not supported");
}

function previewMint(uint256) public view virtual override(ERC4626, IERC4626) returns (uint256) {
revert("ERC7540: previewMint not supported");
}

function previewRedeem(uint256) public view virtual override(ERC4626, IERC4626) returns (uint256) {
revert("ERC7540: previewRedeem not supported");
}

function previewWithdraw(uint256) public view virtual override(ERC4626, IERC4626) returns (uint256) {
revert("ERC7540: previewWithdraw not supported");
}

/**
* @dev Implements ERC-165 interface detection.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(IERC165).interfaceId ||
interfaceId == 0xe3bc4e65 || // ERC-7540 operator methods
interfaceId == 0x2f0a18c5 || // ERC-7575 interface
interfaceId == 0xce3bbe50 || // Asynchronous deposit Vault
interfaceId == 0x620ee8e4; // Asynchronous redemption Vault
}

/**
* @dev Internal function to generates a request ID. Requests created within the same block,
* for the same controller, input, and sender, are cumulative.
Expand All @@ -162,5 +244,17 @@ abstract contract ERC7540 is ERC4626, IERC7540 {
/**
* @dev Abstract function for transitioning requests from Pending to Claimable.
*/
function _processPendingRequests(uint256 requestId, address controller) internal virtual;
function _processPendingRequests(uint256 requestId, address controller) internal virtual {
Request storage depositRequest = _pendingDepositRequests[controller][requestId];
if (depositRequest.amount > 0) {
depositRequest.claimable += depositRequest.amount;
depositRequest.amount = 0;
}

Request storage redeemRequest = _pendingRedeemRequests[controller][requestId];
if (redeemRequest.amount > 0) {
redeemRequest.claimable += redeemRequest.amount;
redeemRequest.amount = 0;
}
}
}

0 comments on commit f46e0f5

Please sign in to comment.