From af5638d9dbd7d1a6eabfb2aba832447ff838b870 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 4 Mar 2026 10:44:00 +0100 Subject: [PATCH 1/6] Revert if a call fails in an atomic batch --- contracts/metatx/ERC2771Forwarder.sol | 6 ++++++ test/metatx/ERC2771Forwarder.test.js | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index a99dc553950..f49d66ab907 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -75,6 +75,11 @@ contract ERC2771Forwarder is EIP712, Nonces { */ event ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success); + /** + * @dev One of the calls in an atomic batch failed. + */ + error ERC2771ForwarderFailureInAtomicBatch(); + /** * @dev The request `from` doesn't match with the recovered `signer`. */ @@ -173,6 +178,7 @@ contract ERC2771Forwarder is EIP712, Nonces { requestsValue += requests[i].value; bool success = _execute(requests[i], atomic); if (!success) { + if (atomic) revert ERC2771ForwarderFailureInAtomicBatch(); refundValue += requests[i].value; } } diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js index 1a8bf2cd2c8..ec8d20b47a5 100644 --- a/test/metatx/ERC2771Forwarder.test.js +++ b/test/metatx/ERC2771Forwarder.test.js @@ -227,6 +227,20 @@ describe('ERC2771Forwarder', function () { expect(await this.forwarder.nonces(request.from)).to.equal(request.nonce + 1n); } }); + + it('atomic batch with reverting request reverts the whole batch', async function () { + // Add extra reverting request + await this.forgeRequest( + { value: 10n, data: this.receiver.interface.encodeFunctionData('mockFunctionRevertsNoReason') }, + this.accounts[requestCount], + ).then(extraRequest => this.requests.push(extraRequest)); + // recompute total value with the extra request + this.value = requestsValue(this.requests); + + await expect( + this.forwarder.executeBatch(this.requests, ethers.ZeroAddress, { value: this.value }), + ).to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderFailureInAtomicBatch'); + }); }); describe('with tampered requests', function () { From b137f20f295274046ef366146f95faf0d020361e Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 4 Mar 2026 10:45:40 +0100 Subject: [PATCH 2/6] add changeset --- .changeset/some-dolls-shine.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/some-dolls-shine.md diff --git a/.changeset/some-dolls-shine.md b/.changeset/some-dolls-shine.md new file mode 100644 index 00000000000..acd010306f9 --- /dev/null +++ b/.changeset/some-dolls-shine.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC2771Forwarder`: Revert the entire atomic batch if one of the call fails. From 27d690c3a6d84a2888140b850a9b67d7f39aa575 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 4 Mar 2026 10:56:01 +0100 Subject: [PATCH 3/6] Update .changeset/some-dolls-shine.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .changeset/some-dolls-shine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/some-dolls-shine.md b/.changeset/some-dolls-shine.md index acd010306f9..3260c5eccfd 100644 --- a/.changeset/some-dolls-shine.md +++ b/.changeset/some-dolls-shine.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -`ERC2771Forwarder`: Revert the entire atomic batch if one of the call fails. +`ERC2771Forwarder`: Revert the entire atomic batch if one of the calls fails. From a2c44f0fb2e2b75375ace1d9e238ab20eabe7477 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 16:14:22 +0100 Subject: [PATCH 4/6] Update ERC2771Forwarder.sol --- contracts/metatx/ERC2771Forwarder.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index f49d66ab907..1a6cd1f9b48 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -178,10 +178,10 @@ contract ERC2771Forwarder is EIP712, Nonces { requestsValue += requests[i].value; bool success = _execute(requests[i], atomic); if (!success) { - if (atomic) revert ERC2771ForwarderFailureInAtomicBatch(); refundValue += requests[i].value; } } + if (atomic && refundValue > 0) revert ERC2771ForwarderFailureInAtomicBatch(); // The batch should revert if there's a mismatched msg.value provided // to avoid request value tampering From 6eda3baaa6e004d18ff01d590f233009e801a6ad Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 16:15:21 +0100 Subject: [PATCH 5/6] Update ERC2771Forwarder.sol --- contracts/metatx/ERC2771Forwarder.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 1a6cd1f9b48..755150ee0d4 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -181,7 +181,6 @@ contract ERC2771Forwarder is EIP712, Nonces { refundValue += requests[i].value; } } - if (atomic && refundValue > 0) revert ERC2771ForwarderFailureInAtomicBatch(); // The batch should revert if there's a mismatched msg.value provided // to avoid request value tampering @@ -192,6 +191,8 @@ contract ERC2771Forwarder is EIP712, Nonces { // Some requests with value were invalid (possibly due to frontrunning). // To avoid leaving ETH in the contract this value is refunded. if (refundValue != 0) { + if (atomic) revert ERC2771ForwarderFailureInAtomicBatch(); + // 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. From 9e1b9914c7b1ec3a531202070889ecd37ae0cd94 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 5 Mar 2026 16:15:47 +0100 Subject: [PATCH 6/6] Apply suggestion from @Amxx --- .changeset/some-dolls-shine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/some-dolls-shine.md b/.changeset/some-dolls-shine.md index 3260c5eccfd..b57a9ef2017 100644 --- a/.changeset/some-dolls-shine.md +++ b/.changeset/some-dolls-shine.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -`ERC2771Forwarder`: Revert the entire atomic batch if one of the calls fails. +`ERC2771Forwarder`: Revert the entire atomic batch if a call with value fails.