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

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

DeeJayElly
Copy link

Fixes #????

PR Checklist

  • Tests
  • Documentation
  • Changeset entry (run npx changeset add)

Copy link

changeset-bot bot commented Jan 24, 2025

⚠️ No Changeset found

Latest commit: 6c2d9f6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Comment on lines +44 to +85
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 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 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 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordering could be more consistent.

Either:

  • requests (deposit + redeem)
  • pending (deposit + redeem) request
  • claimable (deposit + redeem)

Or

  • deposit (request + pending request + claimable)
  • redeem (request + pending request + claimable)

/**
* @dev Abstract implementation of the ERC-7540 standard, extending ERC-4626.
*/
abstract contract ERC7540 is ERC4626, IERC7540, ReentrancyGuard {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is reentrancy guard really necessary? None of the contract in the codebase use it. We always make sure that behaviour is safe without it.

}

// Mappings to track pending and claimable requests
mapping(address => mapping(uint256 => Request)) internal _pendingDepositRequests;
Copy link
Collaborator

@Amxx Amxx Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

address controller,
address owner
) external override nonReentrant returns (uint256 requestId) {
require(assets > 0, "ERC7540: assets must be greater than zero");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better if asset == 0 did not revert, but instead was a no-op.

(see how ERC-20 transfers of amount 0 are supported)

address owner
) external override nonReentrant returns (uint256 requestId) {
require(assets > 0, "ERC7540: assets must be greater than zero");
require(owner == msg.sender || isOperator(owner, msg.sender), "ERC7540: unauthorized");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things here:

  • we avoid using msg.sender and instead use _msgSender() provided by Context. This is necessary for supporting ERC-2771meta-tx.
  • We avoid using revert strings. Instead, we only use custom errors that can give more context. Please see ERC-6093

* @dev Internal function to generate a unique request ID.
*/
function _generateRequestId(address controller, uint256 input) internal view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp, block.number, msg.sender, controller, input)));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using both block.timestamp and block.number is really a duplication of the same thing. We we ever expect one to change but not the other?

Note: on L2, block.number is more reliable.

Regardless, this function helps with uniqueness, but doesn't provide any real guarantee. While it is more expensive, maybe using a counter would be the better option.

Comment on lines +48 to +50
IERC20(asset()).safeTransferFrom(owner, address(this), assets);

_pendingDepositRequests[controller][requestId].amount += assets;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like we should inverse the two lines and remove nonReentrant. WDYT?

Comment on lines +58 to +73
function requestRedeem(
uint256 shares,
address controller,
address owner
) external override nonReentrant returns (uint256 requestId) {
require(shares > 0, "ERC7540: shares must be greater than zero");
require(owner == msg.sender || isOperator(owner, msg.sender), "ERC7540: unauthorized");

requestId = _generateRequestId(controller, shares);

_burn(owner, shares);

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

emit RedeemRequest(controller, owner, requestId, msg.sender, shares);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no external call in this function, why do we need nonReentrant ?

import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol";

contract ERC7540Mock is ERC7540 {
constructor(address asset) ERC20("ERC4626Mock", "E4626M") ERC7540(IERC20(asset)) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some reason, it seems this is breakin, the transpiler

Suggested change
constructor(address asset) ERC20("ERC4626Mock", "E4626M") ERC7540(IERC20(asset)) {}
constructor(IERC20 asset) ERC20("ERC4626Mock", "E4626M") ERC7540(asset) {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants