Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4c1cd22
Minimal Forwarder
ernestognw Jun 13, 2023
2432d0d
Add changeset
ernestognw Jun 13, 2023
6a87cec
Fix ERC2771 tests
ernestognw Jun 14, 2023
16b9ea8
Add batching and better explain gas forwarding check
ernestognw Jun 20, 2023
7c5d038
Lint
ernestognw Jun 20, 2023
5662e35
Applied suggestions
ernestognw Jun 20, 2023
d1c75c8
Fix test
ernestognw Jun 20, 2023
dff9998
Complete tests
ernestognw Jun 21, 2023
8da57ef
Improve testing
ernestognw Jun 21, 2023
5978b7e
Rename MinimalForwarder to ERC2771Forwarder
ernestognw Jun 23, 2023
3d5fe68
Avoid revert on nonce mismatch
ernestognw Jun 23, 2023
c781b6b
Use _validate in _execute
ernestognw Jun 23, 2023
f5987eb
Use timestamp instead of block number
ernestognw Jun 23, 2023
e59d707
Merge branch 'master' into lib-643-production-ready-minimal-forwarder-2
ernestognw Jun 24, 2023
87e77b1
Apply suggestions
ernestognw Jun 24, 2023
bdd061d
Pack signature into request
ernestognw Jun 24, 2023
376f3b2
Fix ERC2771Context tests
ernestognw Jun 24, 2023
1ba8173
Merge branch 'master' into lib-643-production-ready-minimal-forwarder-2
ernestognw Jun 26, 2023
b94a100
Improve comments in _checkForwardedGas
ernestognw Jun 27, 2023
85acfdf
Remove returndata
ernestognw Jun 27, 2023
74d5961
Fix ETH left
ernestognw Jun 27, 2023
c62d927
Changed note
ernestognw Jun 27, 2023
daafff8
Fix codespell
ernestognw Jun 27, 2023
f3d5b44
Fix ETH left in the contract
ernestognw Jun 27, 2023
cb8690e
Fix nonces
ernestognw Jun 27, 2023
68ce4eb
Avoid reentrancy
ernestognw Jun 27, 2023
99a26c4
Apply suggestion
ernestognw Jun 27, 2023
be93a80
Apply suggestion
ernestognw Jun 27, 2023
5c039ea
Improve tests
ernestognw Jun 28, 2023
b7b985e
Remove flaky test
ernestognw Jun 28, 2023
8f84f4e
Hardcode slither version to 0.9.3
ernestognw Jun 28, 2023
8a03cad
Revert on unsuccessful execute
ernestognw Jun 28, 2023
95bcb57
Implement suggestions
ernestognw Jun 29, 2023
8b7e961
tweak proof wording
frangio Jun 29, 2023
f72c2d5
change ETH -> value
frangio Jun 29, 2023
62d2342
adjust comment after recent change
frangio Jun 29, 2023
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
Prev Previous commit
Next Next commit
Fix nonces
  • Loading branch information
ernestognw committed Jun 27, 2023
commit cb8690e4167342c017fb72c7c655ae05834b5a34
36 changes: 14 additions & 22 deletions contracts/metatx/ERC2771Forwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ contract ERC2771Forwarder is EIP712, Nonces {
* is provided.
*/
function verify(ForwardRequestData calldata request) public view virtual returns (bool) {
(bool alive, bool signerMatch, ) = _validate(request, nonces(request.from));
(bool alive, bool signerMatch, ) = _validate(request);
return alive && signerMatch;
}

Expand All @@ -88,7 +88,8 @@ contract ERC2771Forwarder is EIP712, Nonces {
*
* Requirements:
*
* - The request value should be equal to the provided `msg.value`
* - The request value should be equal to the provided `msg.value`.
* - The request should be valid according to {verify}.
*/
function execute(ForwardRequestData calldata request) public payable virtual returns (bool) {
// This check can be before the call because _execute reverts with an invalid request.
Expand All @@ -97,7 +98,7 @@ contract ERC2771Forwarder is EIP712, Nonces {
revert ERC2771ForwarderMismatchedValue(request.value, msg.value);
}

return _execute(request, _useNonce(request.from), true);
return _execute(request, true);
}

/**
Expand All @@ -117,7 +118,7 @@ contract ERC2771Forwarder is EIP712, Nonces {
* Requirements:
*
* - The sum of the requests' values should be equal to the provided `msg.value`
* - All of the request should be valid (see {verify}) when `refundReceiver` is the zero address.
* - All of the requests should be valid (see {verify}) when `refundReceiver` is the zero address.
*
* NOTE: Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for
* the first-level forwarded calls. In case a forwarded request calls to a contract with another
Expand All @@ -134,7 +135,7 @@ contract ERC2771Forwarder is EIP712, Nonces {

for (uint256 i; i < requests.length; ++i) {
requestsValue += requests[i].value;
bool success = _execute(requests[i], _useNonce(requests[i].from), atomic);
bool success = _execute(requests[i], atomic);
if (!success) {
refundValue += requests[i].value;
}
Expand All @@ -149,37 +150,29 @@ contract ERC2771Forwarder is EIP712, Nonces {
// To avoid unexpected reverts because a request was frontrunned leaving ETH in the contract
// the value is sent back instead of reverting.
if (refundValue != 0) {
// We know `refundReceiver != address(0) && requestsValue == msg.value`
// We know refundReceiver != address(0) && requestsValue == msg.value
// meaning we can ensure refundValue is not taken from the original contract's balance
// and refundReceiver is a known account.
Address.sendValue(refundReceiver, refundValue);
}
}

/**
* @dev Validates if the provided request can be executed at current block with `request.signature`
* @dev Validates if the provided request can be executed at current timestamp with `request.signature`
* on behalf of `request.signer`.
*
* Internal function without nonce validation.
*/
function _validate(
ForwardRequestData calldata request,
uint256 nonce
ForwardRequestData calldata request
) internal view virtual returns (bool alive, bool signerMatch, address signer) {
signer = _recoverForwardRequestSigner(request, nonce);
signer = _recoverForwardRequestSigner(request);
return (request.deadline >= block.timestamp, signer == request.from, signer);
}

/**
* @dev Recovers the signer of an EIP712 message hash for a forward `request` and its corresponding `signature`.
* See {ECDSA-recover}.
*
* Internal function without nonce validation.
*/
function _recoverForwardRequestSigner(
ForwardRequestData calldata request,
uint256 nonce
) internal view virtual returns (address) {
function _recoverForwardRequestSigner(ForwardRequestData calldata request) internal view virtual returns (address) {
return
_hashTypedDataV4(
keccak256(
Expand All @@ -189,7 +182,7 @@ contract ERC2771Forwarder is EIP712, Nonces {
request.to,
request.value,
request.gas,
nonce,
nonces(request.from),
request.deadline,
keccak256(request.data)
)
Expand All @@ -215,10 +208,9 @@ contract ERC2771Forwarder is EIP712, Nonces {
*/
function _execute(
ForwardRequestData calldata request,
uint256 nonce,
bool requireValidRequest
) internal virtual returns (bool success) {
(bool alive, bool signerMatch, address signer) = _validate(request, nonce);
(bool alive, bool signerMatch, address signer) = _validate(request);

// Need to explicitly specify if a revert is required since non-reverting is default for
// batches and reversion is opt-in since it could be useful in some scenarios
Expand All @@ -240,7 +232,7 @@ contract ERC2771Forwarder is EIP712, Nonces {

_checkForwardedGas(request);

emit ExecutedForwardRequest(signer, nonce, success);
emit ExecutedForwardRequest(signer, _useNonce(signer), success);
}
}

Expand Down
6 changes: 5 additions & 1 deletion test/metatx/ERC2771Forwarder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ contract('ERC2771Forwarder', function (accounts) {
});

it('succeeds ignoring a request with an invalid nonce', async function () {
this.requestDatas[this.idx].nonce = this.requestDatas[this.idx].nonce + 1;
const correctNonce = await this.forwarder.nonces(this.requestDatas[this.idx].from);
this.requestDatas[this.idx].nonce = correctNonce + 1;
this.requestDatas[this.idx].signature = this.sign(
this.signers[this.idx].getPrivateKey(),
this.requestDatas[this.idx],
Expand All @@ -316,6 +317,9 @@ contract('ERC2771Forwarder', function (accounts) {
const receipt = await this.forwarder.executeBatch(this.requestDatas, accounts[0]);

expect(receipt.logs.filter(({ event }) => event === 'ExecutedForwardRequest').length).to.be.equal(2);
expect(await this.forwarder.nonces(this.requestDatas[this.idx].from)).to.be.bignumber.eq(
web3.utils.toBN(correctNonce),
);
});

it('reverts with at least one valid signature for expired deadline', async function () {
Expand Down