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

Make MinimalForwarder production-ready #4065

Conversation

ernestognw
Copy link
Member

@ernestognw ernestognw commented Feb 22, 2023

Warning
This PR is currently on hold, meaning it's not considered yet safe to use any of the updates here until merged.

Fixes #3884
Fixes #3664

Currently, MinimalForwarder is not considered production-ready since it was designed mainly for testing purposes in the library. However, it's not that far from a MinimalForwarder that's something we may recommend.

The new MinimalForwarder should guarantee:

  • Backwards compatibility
  • Security (and thorough documentation about it)
  • A deadline for expiring transactions

PR Checklist

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

@changeset-bot
Copy link

changeset-bot bot commented Feb 22, 2023

⚠️ No Changeset found

Latest commit: 4c1cd22

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

Copy link
Collaborator

@Amxx Amxx left a comment

Choose a reason for hiding this comment

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

Left some comments

keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)");
bytes32 private constant _EXPIRING_FORWARD_REQUEST_TYPEHASH =
keccak256(
"ExpiringForwardRequest(uint48 deadline,address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)"
Copy link
Collaborator

Choose a reason for hiding this comment

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

why put the deadline as the first argument? I would personnaly put it after the nonce.

Copy link
Member Author

@ernestognw ernestognw Feb 23, 2023

Choose a reason for hiding this comment

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

Made sense to me to pack them this way. It's not a big deal since it's calldata, but I thought it was better to put the uint48 next to an address

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe ABI encoding does not pack values. Every value takes up 32 bytes.

constructor() EIP712("MinimalForwarder", "0.0.1") {}
constructor() EIP712(name(), version()) {}

function version() public view virtual returns (string memory) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we have this function ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Customizing EIP712 version, similar to Governor:

constructor(string memory name_) EIP712(name_, version()) {

I can't see immediate reasons why someone would override it, but I feel it makes sense to leave the option open? What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

I can't neither think about a reason why someone would need it, but I'd say the same logic of why we set virtual in public functions should apply here.

"Why would someone override version and name?" is kinda the same question as "Why would someone override X function?" from my perspective.

No strong opinion if we remove it.


function getNonce(address from) public view returns (uint256) {
function name() public view virtual returns (string memory) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we have this function ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the reason was to customize the EIP712 name, but here I don't agree that it makes sense to make it customizable. It should be fine to make it always "MinimalForwarder".

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as #4065 (comment)


mapping(address => uint256) private _nonces;

constructor() EIP712("MinimalForwarder", "0.0.1") {}
constructor() EIP712(name(), version()) {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

why change from constructor() EIP712("MinimalForwarder", "0.0.1") {} ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Replied in your comments about version() and name().

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as #4065 (comment)

contracts/metatx/MinimalForwarder.sol Outdated Show resolved Hide resolved
contracts/metatx/MinimalForwarder.sol Outdated Show resolved Hide resolved
test/metatx/MinimalForwarder.test.js Outdated Show resolved Hide resolved
Comment on lines 104 to 108
bool expired = block.timestamp >= req.deadline;
return
getNonce(req.from) == req.nonce && // Signed nonce corresponds with current nonce
signer == req.from && // From is signer
!expired;
Copy link
Collaborator

@Amxx Amxx Feb 23, 2023

Choose a reason for hiding this comment

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

Suggested change
bool expired = block.timestamp >= req.deadline;
return
getNonce(req.from) == req.nonce && // Signed nonce corresponds with current nonce
signer == req.from && // From is signer
!expired;
return
getNonce(req.from) == req.nonce && // Signed nonce corresponds with current nonce
signer == req.from && // From is signer
block.timestamp < req.deadline; // Expired

@@ -69,4 +99,51 @@ contract MinimalForwarder is EIP712 {

return (success, returndata);
}

function _verify(ExpiringForwardRequest memory req, address signer) private view returns (bool) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that we're missing require(msg.value == req.value).

@ernestognw ernestognw added the on hold Put on hold for some reason that must be specified in a comment. label Feb 28, 2023
return _execute(req);
}

function _execute(ExpiringForwardRequest memory req) private returns (bool, bytes memory) {
Copy link

@MerlinEgalite MerlinEgalite Mar 22, 2023

Choose a reason for hiding this comment

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

Is the fact that _execute is not reverting when the call fails intended and left to the contract inheriting from MinimalForwarder?

If yes and given that this version will be considered as production ready, it would be nice to add a warning about this to avoid silent failure on the implementation side. Else, I think we should revert while bubbling up the error.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, its an intended behavior.

This PR is on hold. The code it contains should not be seen as a production-ready version of the forwarder. For sure we will want to challenge that design choice (IMO there are good reason for this behavior), and document it.

Choose a reason for hiding this comment

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

Clear, thx for the answer.

Copy link
Member Author

Choose a reason for hiding this comment

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

➕ to @Amxx explanation.

I'll leave this open and I added a note at the top of the PR indicating this is currently on hold.

@ernestognw ernestognw force-pushed the lib-643-production-ready-minimal-forwarder branch from 5bee13c to 4c1cd22 Compare June 13, 2023 03:45
@ernestognw ernestognw closed this Jun 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
on hold Put on hold for some reason that must be specified in a comment.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider removing MinimalForwarder Loss of unused Ether in meta-transaction
4 participants