From 232633e4370402b1c58896281771247795884901 Mon Sep 17 00:00:00 2001 From: bl0up Date: Thu, 27 Jun 2024 07:10:07 +0200 Subject: [PATCH] fix: forwarder security (#3) ## Summary by CodeRabbit - **Refactor** - Refactored the date calculation logic in the `BokkyPooBahsDateTimeLibrary` for efficiency and readability. - **New Features** - Introduced remappings for paths like `@openzeppelin`, `forge-std`, and `hardhat` for easier directory navigation. - Refactored the `Forwarder` contract to inherit from `ERC2771Forwarder`, simplifying contract structure. - Updated functionality in `ERC721Whitelist` contract for Merkle proof verification. - Added functionality in the `CallReceiverMock` contract for testing purposes. - Updated access control in the `MetaDog` and `ExampleERC721` contracts with `onlyOwner` modifiers. - Implemented claim and withdrawal functionalities in the `CouponFacet` contract for bonds. - Enhanced token transfer logic in the `BondFacet` contract and added new interfaces. - Optimized date calculation functions in the `BokkyPooBahsDateTimeLibrary`. - **Documentation** - Updated comments and variable names in the date calculation functions. --- contracts/Forwarder.sol | 92 +------ contracts/mocks/CallReceiverMock.sol | 78 ++++++ ignition/modules/main.ts | 2 +- remappings.txt | 2 - subgraph/datasources/forwarder.gql.json | 57 ----- subgraph/datasources/forwarder.ts | 48 ---- subgraph/datasources/forwarder.yaml | 22 -- subgraph/fetch/account.ts | 8 - subgraph/fetch/forwarder.ts | 19 -- subgraph/subgraph.config.json | 6 - test/Forwarder.t.sol | 314 +++++++++--------------- test/GenericTokenMeta.t.sol | 10 +- 12 files changed, 204 insertions(+), 454 deletions(-) create mode 100644 contracts/mocks/CallReceiverMock.sol delete mode 100644 subgraph/datasources/forwarder.gql.json delete mode 100644 subgraph/datasources/forwarder.ts delete mode 100644 subgraph/datasources/forwarder.yaml delete mode 100644 subgraph/fetch/account.ts delete mode 100644 subgraph/fetch/forwarder.ts diff --git a/contracts/Forwarder.sol b/contracts/Forwarder.sol index 21b7033c..ad21927d 100644 --- a/contracts/Forwarder.sol +++ b/contracts/Forwarder.sol @@ -1,92 +1,10 @@ // SPDX-License-Identifier: MIT -// SettleMint.com +// OpenZeppelin Contracts (last updated v5.0.0) (metatx/ERC2771Forwarder.sol) -pragma solidity ^0.8.24; +pragma solidity 0.8.24; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {ERC2771Forwarder} from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; -/** - * @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}. - */ -contract Forwarder is EIP712, ERC165 { - using ECDSA for bytes32; - - struct ForwardRequest { - address from; - address to; - uint256 value; - uint256 gas; - uint256 nonce; - bytes data; - } - - event ForwarderCreated(address indexed forwarderAddress); - event MetaTransactionExecuted(address indexed from, address indexed to); - - bytes32 private constant _TYPEHASH = - keccak256( - "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)" - ); - - mapping(address => uint256) private _nonces; - - constructor() EIP712("MinimalForwarder", "0.0.1") { - emit ForwarderCreated(address(this)); - } - - function getNonce(address from) public view returns (uint256) { - return _nonces[from]; - } - - function verify( - ForwardRequest calldata req, - bytes calldata signature - ) public view returns (bool) { - address signer = _hashTypedDataV4( - keccak256( - abi.encode( - _TYPEHASH, - req.from, - req.to, - req.value, - req.gas, - req.nonce, - keccak256(req.data) - ) - ) - ).recover(signature); - return _nonces[req.from] == req.nonce && signer == req.from; - } - - function execute( - ForwardRequest calldata req, - bytes calldata signature - ) public payable returns (bool, bytes memory) { - require( - verify(req, signature), - "MinimalForwarder: signature does not match request" - ); - _nonces[req.from] = req.nonce + 1; - - (bool success, bytes memory returndata) = req.to.call{ - gas: req.gas, - value: req.value - }(abi.encodePacked(req.data, req.from)); - // Validate that the relayer has sent enough gas for the call. - // See https://ronan.eth.link/blog/ethereum-gas-dangers/ - assert(gasleft() > req.gas / 63); - - emit MetaTransactionExecuted(req.from, req.to); - return (success, returndata); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC165) returns (bool) { - return - interfaceId == type(EIP712).interfaceId || - super.supportsInterface(interfaceId); - } +contract Forwarder is ERC2771Forwarder { + constructor(string memory name) payable ERC2771Forwarder(name) {} } diff --git a/contracts/mocks/CallReceiverMock.sol b/contracts/mocks/CallReceiverMock.sol new file mode 100644 index 00000000..5aae5bb8 --- /dev/null +++ b/contracts/mocks/CallReceiverMock.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract CallReceiverMock is Ownable { + event MockFunctionCalled(); + event MockFunctionCalledWithArgs(uint256 a, uint256 b); + + uint256[] private _array; + + constructor() payable Ownable(msg.sender){} + + function mockFunction() public payable returns (string memory) { + emit MockFunctionCalled(); + return "0x1234"; + } + + function mockFunctionEmptyReturn() public payable { + emit MockFunctionCalled(); + } + + function mockFunctionWithArgs(uint256 a, uint256 b) public payable returns (string memory) { + emit MockFunctionCalledWithArgs(a, b); + return "0x1234"; + } + + function mockFunctionNonPayable() public returns (string memory) { + emit MockFunctionCalled(); + return "0x1234"; + } + + function mockStaticFunction() public pure returns (string memory) { + return "0x1234"; + } + + function mockFunctionRevertsNoReason() public payable { + revert(); + } + + function mockFunctionRevertsReason() public payable { + revert("CallReceiverMock: reverting"); + } + + function mockFunctionThrows() public payable { + assert(false); + } + + function mockFunctionOutOfGas() public payable { + for (uint256 i = 0; ; ++i) { + _array.push(i); + } + } + + function mockFunctionWritesStorage(bytes32 slot, bytes32 value) public returns (string memory) { + assembly { + sstore(slot, value) + } + return "0x1234"; + } + + function withdraw() external onlyOwner { + payable(owner()).transfer(address(this).balance); + } +} + +contract CallReceiverMockTrustingForwarder is CallReceiverMock { + address private _trustedForwarder; + + constructor(address trustedForwarder_) { + _trustedForwarder = trustedForwarder_; + } + + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == _trustedForwarder; + } +} diff --git a/ignition/modules/main.ts b/ignition/modules/main.ts index cf70ffad..1cd532ca 100644 --- a/ignition/modules/main.ts +++ b/ignition/modules/main.ts @@ -1,7 +1,7 @@ import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'; const GenericTokenMetaModule = buildModule('GenericTokenMetaModule', (m) => { - const forwarder = m.contract('Forwarder'); + const forwarder = m.contract('Forwarder', ['Forwarder']); const token = m.contract('GenericTokenMeta', [ 'GenericERC20', 'GT', diff --git a/remappings.txt b/remappings.txt index 49070e7d..feaba2dd 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1 @@ -@openzeppelin/=node_modules/@openzeppelin/ forge-std/=lib/forge-std/src/ -hardhat/=node_modules/hardhat/ diff --git a/subgraph/datasources/forwarder.gql.json b/subgraph/datasources/forwarder.gql.json deleted file mode 100644 index be74d9a0..00000000 --- a/subgraph/datasources/forwarder.gql.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "name": "Account", - "fields": [ - { "name": "id", "type": "Bytes!" }, - { "name": "asForwarder", "type": "ForwarderContract" }, - { "name": "request", "type": "ForwarderRequest" }, - { - "name": "ForwarderCreatedEvent", - "type": "ForwarderCreated!", - "derived": "forwarderAddress" - } - ] - }, - { - "name": "ForwarderContract", - "immutable": true, - "fields": [{ "name": "asAccount", "type": "Account!" }] - }, - { - "name": "ForwarderRequest", - "fields": [ - { "name": "contract", "type": "ForwarderContract" }, - { "name": "from", "type": "Account!" }, - { "name": "to", "type": "Account!" }, - { "name": "value", "type": "BigInt!" }, - { "name": "gas", "type": "BigInt!" }, - { "name": "nonce", "type": "BigInt!" }, - { "name": "data", "type": "String!" } - ] - }, - { - "name": "ForwarderCreated", - "immutable": true, - "parent": "Event", - "fields": [ - { "name": "emitter", "type": "Account!" }, - { "name": "transaction", "type": "Transaction!" }, - { "name": "timestamp", "type": "BigInt!" }, - { "name": "contract", "type": "ForwarderContract!" }, - { "name": "forwarderAddress", "type": "Account!" } - ] - }, - { - "name": "MetaTransactionExecuted", - "immutable": true, - "parent": "Event", - "fields": [ - { "name": "emitter", "type": "Account!" }, - { "name": "transaction", "type": "Transaction!" }, - { "name": "timestamp", "type": "BigInt!" }, - { "name": "contract", "type": "ForwarderContract!" }, - { "name": "from", "type": "Account!" }, - { "name": "to", "type": "Account!" } - ] - } -] diff --git a/subgraph/datasources/forwarder.ts b/subgraph/datasources/forwarder.ts deleted file mode 100644 index 12cc74c0..00000000 --- a/subgraph/datasources/forwarder.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { events, transactions } from '@amxx/graphprotocol-utils'; -import { Bytes } from '@graphprotocol/graph-ts'; -import { fetchAccount } from '../fetch/account'; -import { fetchForwarder } from '../fetch/forwarder'; -import { - ForwarderCreated as ForwarderCreatedEvent, - MetaTransactionExecuted as MetaTransactionEvent, -} from '../../generated/forwarder/Forwarder'; -import { - ForwarderCreated, - MetaTransactionExecuted, -} from '../../generated/schema'; - -export function handleForwarderCreated(event: ForwarderCreatedEvent): void { - const contract = fetchForwarder(event.address); - fetchAccount(event.params.forwarderAddress); - - const ev = new ForwarderCreated(events.id(event)); - ev.emitter = Bytes.fromHexString(contract.id); - ev.transaction = transactions.log(event).id; - ev.timestamp = event.block.timestamp; - - ev.contract = contract.id; - ev.forwarderAddress = Bytes.fromHexString( - event.params.forwarderAddress.toHexString() - ); - - ev.save(); -} - -export function handleMetaTransactionExecuted( - event: MetaTransactionEvent -): void { - const contract = fetchForwarder(event.address); - fetchAccount(event.params.from); - fetchAccount(event.params.to); - - const ev = new MetaTransactionExecuted(events.id(event)); - ev.emitter = Bytes.fromHexString(contract.id); - ev.transaction = transactions.log(event).id; - ev.timestamp = event.block.timestamp; - - ev.contract = contract.id; - ev.from = Bytes.fromHexString(event.params.from.toHexString()); - ev.to = Bytes.fromHexString(event.params.to.toHexString()); - - ev.save(); -} diff --git a/subgraph/datasources/forwarder.yaml b/subgraph/datasources/forwarder.yaml deleted file mode 100644 index 7366feba..00000000 --- a/subgraph/datasources/forwarder.yaml +++ /dev/null @@ -1,22 +0,0 @@ - - kind: ethereum/contract - name: {id} - network: {chain} - source: - address: "{address}" - abi: Forwarder - startBlock: {startBlock} - mapping: - kind: ethereum/events - apiVersion: 0.0.5 - language: wasm/assemblyscript - entities: - - Forwarder - abis: - - name: Forwarder - file: {root}/out/Forwarder.sol/Forwarder.json - eventHandlers: - - event: ForwarderCreated(indexed address) - handler: handleForwarderCreated - - event: MetaTransactionExecuted(indexed address,indexed address) - handler: handleMetaTransactionExecuted - file: {file} \ No newline at end of file diff --git a/subgraph/fetch/account.ts b/subgraph/fetch/account.ts deleted file mode 100644 index 409cfe56..00000000 --- a/subgraph/fetch/account.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Address, Bytes } from '@graphprotocol/graph-ts'; -import { Account } from '../../generated/schema'; - -export function fetchAccount(address: Address): Account { - const account = new Account(Bytes.fromHexString(address.toHex())); - account.save(); - return account; -} diff --git a/subgraph/fetch/forwarder.ts b/subgraph/fetch/forwarder.ts deleted file mode 100644 index 1a7d2b42..00000000 --- a/subgraph/fetch/forwarder.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Address } from '@graphprotocol/graph-ts'; -import { ForwarderContract } from '../../generated/schema'; -import { fetchAccount } from './account'; - -export function fetchForwarder(address: Address): ForwarderContract { - const account = fetchAccount(address); - let contract = ForwarderContract.load(account.id.toHex()); - - if (contract == null) { - contract = new ForwarderContract(account.id.toHex()); - contract.asAccount = account.id; - account.asForwarder = contract.id; - - contract.save(); - account.save(); - } - - return contract as ForwarderContract; -} diff --git a/subgraph/subgraph.config.json b/subgraph/subgraph.config.json index c26cc174..7ecaaf7f 100644 --- a/subgraph/subgraph.config.json +++ b/subgraph/subgraph.config.json @@ -7,12 +7,6 @@ "address": "0x0000000000000000000000000000000000000000", "startBlock": 0, "module": ["erc20", "pausable", "accesscontrol"] - }, - { - "name": "Forwarder", - "address": "0x0000000000000000000000000000000000000000", - "startBlock": 0, - "module": ["forwarder"] } ] } diff --git a/test/Forwarder.t.sol b/test/Forwarder.t.sol index f5f9def7..7af5eb8e 100644 --- a/test/Forwarder.t.sol +++ b/test/Forwarder.t.sol @@ -1,238 +1,158 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -import "forge-std/Test.sol"; -import "../contracts/Forwarder.sol"; -import "../contracts/GenericTokenMeta.sol"; +pragma solidity ^0.8.24; -contract ForwarderTest is Test { - Forwarder forwarder; - GenericTokenMeta token; - address owner; - address signer; - address signer2; - uint256 privateKey; - uint256 privateKey2; +import {Test} from "forge-std/Test.sol"; +import {Forwarder} from "../contracts/Forwarder.sol"; +import {ERC2771Forwarder} from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; +import {CallReceiverMockTrustingForwarder, CallReceiverMock} from "../contracts/mocks/CallReceiverMock.sol"; + +struct ForwardRequest { + address from; + address to; + uint256 value; + uint256 gas; + uint256 nonce; + uint48 deadline; + bytes data; +} - function setUp() public { - forwarder = new Forwarder(); - owner = address(this); - token = new GenericTokenMeta( - "GenericTokenMeta", - "GMT", - address(forwarder) +contract ERC2771ForwarderMock is Forwarder { + constructor(string memory name) Forwarder(name) {} + + function structHash(ForwardRequest calldata request) external view returns (bytes32) { + return _hashTypedDataV4( + keccak256( + abi.encode( + _FORWARD_REQUEST_TYPEHASH, + request.from, + request.to, + request.value, + request.gas, + request.nonce, + request.deadline, + keccak256(request.data) + ) + ) ); - privateKey = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; - privateKey2 = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; - signer = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; - signer2 = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; - token.mint(signer, 20); } +} - function testNonce() public view { - uint256 nonce = forwarder.getNonce(signer); - assertEq(nonce, 0); - } +contract ERC2771ForwarderTest is Test { + ERC2771ForwarderMock internal _erc2771Forwarder; + CallReceiverMockTrustingForwarder internal _receiver; - function testVerify() public view { - bytes32 domainSeparator = keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ), - keccak256(bytes("MinimalForwarder")), // Name - keccak256(bytes("0.0.1")), // Version - block.chainid, - address(forwarder) - ) - ); + uint256 internal _signerPrivateKey; + uint256 internal _relayerPrivateKey; - bytes4 transferSelector = bytes4( - keccak256("transfer(address,uint256)") - ); - bytes memory data = abi.encodeWithSelector(transferSelector, owner, 10); + address internal _signer; + address internal _relayer; - uint256 nonce = forwarder.getNonce(signer); + uint256 internal constant _MAX_ETHER = 10_000_000; // To avoid overflow - Forwarder.ForwardRequest memory req = Forwarder.ForwardRequest({ - from: signer, - to: address(token), - value: 0, - gas: 300_000, - nonce: nonce, - data: data - }); + function setUp() public { + _erc2771Forwarder = new ERC2771ForwarderMock("ERC2771Forwarder"); + _receiver = new CallReceiverMockTrustingForwarder(address(_erc2771Forwarder)); - bytes32 structHash = keccak256( - abi.encode( - keccak256( - "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)" - ), - req.from, - req.to, - req.value, - req.gas, - req.nonce, - keccak256(req.data) - ) - ); - bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", domainSeparator, structHash) - ); + _signerPrivateKey = 0xA11CE; + _relayerPrivateKey = 0xB0B; - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + _signer = vm.addr(_signerPrivateKey); + _relayer = vm.addr(_relayerPrivateKey); + } - // Adjust the `v` value if necessary - if (v == 0 || v == 1) { - v += 27; - } + function _forgeRequestData(uint256 value, uint256 nonce, uint48 deadline, bytes memory data) + private + view + returns (Forwarder.ForwardRequestData memory) + { + ForwardRequest memory request = ForwardRequest({ + from: _signer, + to: address(_receiver), + value: value, + gas: 30000, + nonce: nonce, + deadline: deadline, + data: data + }); + bytes32 digest = _erc2771Forwarder.structHash(request); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, digest); bytes memory signature = abi.encodePacked(r, s, v); - forwarder.verify(req, signature); - - //signing with a different private key - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(privateKey2, digest); - - // Adjust the `v` value if necessary - if (v1 == 0 || v1 == 1) { - v1 += 27; - } - bytes memory signature1 = abi.encodePacked(r1, s1, v1); - bool result = forwarder.verify(req, signature1); - assertEq(result, false); - - //changing the request - req.nonce++; - result = forwarder.verify(req, signature); - assertEq(result, false); + return ERC2771Forwarder.ForwardRequestData({ + from: request.from, + to: request.to, + value: request.value, + gas: request.gas, + deadline: request.deadline, + data: request.data, + signature: signature + }); } - function testExecuteInvalidSignature() public { - bytes32 domainSeparator = keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ), - keccak256(bytes("MinimalForwarder")), // Name - keccak256(bytes("0.0.1")), // Version - block.chainid, - address(forwarder) - ) - ); + function testExecuteAvoidsETHStuck(uint256 initialBalance, uint256 value, bool targetReverts) public { + initialBalance = bound(initialBalance, 0, _MAX_ETHER); + value = bound(value, 0, _MAX_ETHER); - bytes4 transferSelector = bytes4( - keccak256("transfer(address,uint256)") - ); - bytes memory data = abi.encodeWithSelector(transferSelector, owner, 10); + vm.deal(address(_erc2771Forwarder), initialBalance); + + uint256 nonce = _erc2771Forwarder.nonces(_signer); - uint256 nonce = forwarder.getNonce(signer); + vm.deal(address(this), value); - Forwarder.ForwardRequest memory req = Forwarder.ForwardRequest({ - from: signer, - to: address(token), - value: 0, - gas: 300_000, + Forwarder.ForwardRequestData memory requestData = _forgeRequestData({ + value: value, nonce: nonce, - data: data + deadline: uint48(block.timestamp + 1), + data: targetReverts + ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) + : abi.encodeCall(CallReceiverMock.mockFunction, ()) }); - bytes32 structHash = keccak256( - abi.encode( - keccak256( - "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)" - ), - req.from, - req.to, - req.value, - req.gas, - req.nonce, - keccak256(req.data) - ) - ); - bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", domainSeparator, structHash) - ); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - - // Adjust the `v` value if necessary - if (v == 0 || v == 1) { - v += 27; + if (targetReverts) { + vm.expectRevert(); } - v++; - bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert(); - forwarder.execute(req, signature); + _erc2771Forwarder.execute{value: value}(requestData); + assertEq(address(_erc2771Forwarder).balance, initialBalance); } - function testMetaTransactionExecution() public { - bytes32 domainSeparator = keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ), - keccak256(bytes("MinimalForwarder")), // Name - keccak256(bytes("0.0.1")), // Version - block.chainid, - address(forwarder) - ) - ); + function testExecuteBatchAvoidsETHStuck(uint256 initialBalance, uint256 batchSize, uint256 value) public { + batchSize = bound(batchSize, 1, 10); + initialBalance = bound(initialBalance, 0, _MAX_ETHER); + value = bound(value, 0, _MAX_ETHER); - bytes4 transferSelector = bytes4( - keccak256("transfer(address,uint256)") - ); - bytes memory data = abi.encodeWithSelector(transferSelector, owner, 10); + vm.deal(address(_erc2771Forwarder), initialBalance); + uint256 nonce = _erc2771Forwarder.nonces(_signer); - uint256 nonce = forwarder.getNonce(signer); + Forwarder.ForwardRequestData[] memory batchRequestDatas = + new Forwarder.ForwardRequestData[](batchSize); - Forwarder.ForwardRequest memory req = Forwarder.ForwardRequest({ - from: signer, - to: address(token), - value: 0, - gas: 300_000, - nonce: nonce, - data: data - }); + uint256 expectedRefund; - bytes32 structHash = keccak256( - abi.encode( - keccak256( - "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)" - ), - req.from, - req.to, - req.value, - req.gas, - req.nonce, - keccak256(req.data) - ) - ); - bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", domainSeparator, structHash) - ); + for (uint256 i = 0; i < batchSize; ++i) { + bytes memory data; + bool succeed = uint256(keccak256(abi.encodePacked(initialBalance, i))) % 2 == 0; - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + if (succeed) { + data = abi.encodeCall(CallReceiverMock.mockFunction, ()); + } else { + expectedRefund += value; + data = abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()); + } - // Adjust the `v` value if necessary - if (v == 0 || v == 1) { - v += 27; + batchRequestDatas[i] = + _forgeRequestData({value: value, nonce: nonce + i, deadline: uint48(block.timestamp + 1), data: data}); } - bytes memory signature = abi.encodePacked(r, s, v); + address payable refundReceiver = payable(address(0xebe)); + uint256 totalValue = value * batchSize; - // Execute the forwarder request with the signature - (bool success, ) = forwarder.execute(req, signature); - assertTrue(success, "Meta transaction execution failed"); - assertEq(token.balanceOf(signer), 10); - } + vm.deal(address(this), totalValue); + _erc2771Forwarder.executeBatch{value: totalValue}(batchRequestDatas, refundReceiver); - function testSupportsInterface() public view { - bool result = forwarder.supportsInterface(type(IERC165).interfaceId); - assertTrue( - result, - "supportsInterface should return true for IERC165 interface" - ); + assertEq(address(_erc2771Forwarder).balance, initialBalance); + assertEq(refundReceiver.balance, expectedRefund); } } diff --git a/test/GenericTokenMeta.t.sol b/test/GenericTokenMeta.t.sol index 9e6a3e67..47b91d0c 100644 --- a/test/GenericTokenMeta.t.sol +++ b/test/GenericTokenMeta.t.sol @@ -15,12 +15,8 @@ contract GenericTokenMetaTest is Test { function setUp() public { owner = address(this); recipient = address(0x123); - forwarder = new Forwarder(); - token = new GenericTokenMeta( - "GenericTokenMeta", - "GTM", - address(forwarder) - ); + forwarder = new Forwarder("ERC2771"); + token = new GenericTokenMeta("GenericTokenMeta", "GTM", address(forwarder)); } function testMint() public { @@ -62,7 +58,7 @@ contract GenericTokenMetaTest is Test { token.burn(burnAmount); // This should fail } - function testMsgData() public { + function testMsgData() public view { // Call the msgData function bytes memory data = token.msgData(); // Check if the returned data matches the msg.data