diff --git a/contracts/base/ERC20Base.sol b/contracts/base/ERC20Base.sol index bfbd65592..31d05a54d 100644 --- a/contracts/base/ERC20Base.sol +++ b/contracts/base/ERC20Base.sol @@ -103,4 +103,9 @@ contract ERC20Base is ContractMetadata, Multicall, Ownable, ERC20Permit, IMintab function _canSetOwner() internal view virtual override returns (bool) { return msg.sender == owner(); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/base/ERC20Drop.sol b/contracts/base/ERC20Drop.sol index e197e6203..60c61925a 100644 --- a/contracts/base/ERC20Drop.sol +++ b/contracts/base/ERC20Drop.sol @@ -149,4 +149,9 @@ contract ERC20Drop is ContractMetadata, Multicall, Ownable, ERC20Permit, Primary function _canSetPrimarySaleRecipient() internal view virtual override returns (bool) { return msg.sender == owner(); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/base/ERC20DropVote.sol b/contracts/base/ERC20DropVote.sol index 46020ef79..4150ac0e1 100644 --- a/contracts/base/ERC20DropVote.sol +++ b/contracts/base/ERC20DropVote.sol @@ -127,4 +127,9 @@ contract ERC20DropVote is ContractMetadata, Multicall, Ownable, ERC20Votes, Prim function _canSetPrimarySaleRecipient() internal view virtual override returns (bool) { return msg.sender == owner(); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/base/ERC20Vote.sol b/contracts/base/ERC20Vote.sol index 7a13bb852..79626a830 100644 --- a/contracts/base/ERC20Vote.sol +++ b/contracts/base/ERC20Vote.sol @@ -103,4 +103,9 @@ contract ERC20Vote is ContractMetadata, Multicall, Ownable, ERC20Votes, IMintabl function _canSetOwner() internal view virtual override returns (bool) { return msg.sender == owner(); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/base/ERC721Base.sol b/contracts/base/ERC721Base.sol index 49cc41284..3d256e3f6 100644 --- a/contracts/base/ERC721Base.sol +++ b/contracts/base/ERC721Base.sol @@ -202,4 +202,9 @@ contract ERC721Base is ERC721AQueryable, ContractMetadata, Multicall, Ownable, R function _canSetRoyaltyInfo() internal view virtual override returns (bool) { return msg.sender == owner(); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/base/ERC721Drop.sol b/contracts/base/ERC721Drop.sol index a4d2e5a88..42912647a 100644 --- a/contracts/base/ERC721Drop.sol +++ b/contracts/base/ERC721Drop.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; /// @author thirdweb -import { ERC721A } from "../eip/ERC721AVirtualApprove.sol"; +import { ERC721A, Context } from "../eip/ERC721AVirtualApprove.sol"; import "../extension/ContractMetadata.sol"; import "../extension/Multicall.sol"; @@ -304,4 +304,9 @@ contract ERC721Drop is function _dropMsgSender() internal view virtual override returns (address) { return msg.sender; } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/base/ERC721LazyMint.sol b/contracts/base/ERC721LazyMint.sol index 84c23d61d..71929b22d 100644 --- a/contracts/base/ERC721LazyMint.sol +++ b/contracts/base/ERC721LazyMint.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; /// @author thirdweb -import { ERC721A } from "../eip/ERC721AVirtualApprove.sol"; +import { ERC721A, Context } from "../eip/ERC721AVirtualApprove.sol"; import "../extension/ContractMetadata.sol"; import "../extension/Multicall.sol"; @@ -211,4 +211,9 @@ contract ERC721LazyMint is function _canSetRoyaltyInfo() internal view virtual override returns (bool) { return msg.sender == owner(); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/base/ERC721Multiwrap.sol b/contracts/base/ERC721Multiwrap.sol index 9d6873325..a2ee916f1 100644 --- a/contracts/base/ERC721Multiwrap.sol +++ b/contracts/base/ERC721Multiwrap.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; /// @author thirdweb -import { ERC721A } from "../eip/ERC721AVirtualApprove.sol"; +import { ERC721A, Context } from "../eip/ERC721AVirtualApprove.sol"; import "../extension/ContractMetadata.sol"; import "../extension/Ownable.sol"; @@ -244,4 +244,9 @@ contract ERC721Multiwrap is Multicall, TokenStore, SoulboundERC721A, ERC721A, Co function _canSetRoyaltyInfo() internal view virtual override returns (bool) { return msg.sender == owner(); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Context) returns (address) { + return msg.sender; + } } diff --git a/contracts/extension/Multicall.sol b/contracts/extension/Multicall.sol index 25e51cdf8..043d6c3c0 100644 --- a/contracts/extension/Multicall.sol +++ b/contracts/extension/Multicall.sol @@ -19,11 +19,22 @@ contract Multicall is IMulticall { * @param data The bytes data that makes up the batch of function calls to execute. * @return results The bytes data that makes up the result of the batch of function calls executed. */ - function multicall(bytes[] calldata data) external virtual override returns (bytes[] memory results) { + function multicall(bytes[] calldata data) external returns (bytes[] memory results) { results = new bytes[](data.length); + address sender = _msgSender(); + bool isForwarder = msg.sender != sender; for (uint256 i = 0; i < data.length; i++) { - results[i] = Address.functionDelegateCall(address(this), data[i]); + if (isForwarder) { + results[i] = Address.functionDelegateCall(address(this), abi.encodePacked(data[i], sender)); + } else { + results[i] = Address.functionDelegateCall(address(this), data[i]); + } } return results; } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } } diff --git a/contracts/infra/ContractPublisher.sol b/contracts/infra/ContractPublisher.sol index d3f5d883c..0b7f98ab6 100644 --- a/contracts/infra/ContractPublisher.sol +++ b/contracts/infra/ContractPublisher.sol @@ -231,7 +231,7 @@ contract ContractPublisher is IContractPublisher, ERC2771Context, AccessControlE } /// @dev ERC2771Context overrides - function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) { + function _msgSender() internal view virtual override(Context, ERC2771Context, Multicall) returns (address sender) { return ERC2771Context._msgSender(); } diff --git a/contracts/infra/TWFactory.sol b/contracts/infra/TWFactory.sol index 7d88d1636..0e47d7114 100644 --- a/contracts/infra/TWFactory.sol +++ b/contracts/infra/TWFactory.sol @@ -130,7 +130,7 @@ contract TWFactory is Multicall, ERC2771Context, AccessControlEnumerable, IContr return implementation[_type][currentVersion[_type]]; } - function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) { + function _msgSender() internal view virtual override(Context, ERC2771Context, Multicall) returns (address sender) { return ERC2771Context._msgSender(); } diff --git a/contracts/infra/TWFee.sol b/contracts/infra/TWFee.sol index 430f79240..a6a1f3064 100644 --- a/contracts/infra/TWFee.sol +++ b/contracts/infra/TWFee.sol @@ -152,7 +152,7 @@ contract TWFee is ITWFee, Multicall, ERC2771Context, AccessControlEnumerable, IF // ===== Getters ===== - function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) { + function _msgSender() internal view virtual override(Context, ERC2771Context, Multicall) returns (address sender) { return ERC2771Context._msgSender(); } diff --git a/contracts/infra/TWMultichainRegistry.sol b/contracts/infra/TWMultichainRegistry.sol index 772b98674..888ee0a06 100644 --- a/contracts/infra/TWMultichainRegistry.sol +++ b/contracts/infra/TWMultichainRegistry.sol @@ -106,7 +106,7 @@ contract TWMultichainRegistry is ITWMultichainRegistry, Multicall, ERC2771Contex metadataUri = addressToMetadataUri[_chainId][_deployment]; } - function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) { + function _msgSender() internal view virtual override(Context, ERC2771Context, Multicall) returns (address sender) { return ERC2771Context._msgSender(); } diff --git a/contracts/infra/TWRegistry.sol b/contracts/infra/TWRegistry.sol index c4f6667a3..5c42436e9 100644 --- a/contracts/infra/TWRegistry.sol +++ b/contracts/infra/TWRegistry.sol @@ -60,7 +60,7 @@ contract TWRegistry is Multicall, ERC2771Context, AccessControlEnumerable { return deployments[_deployer].length(); } - function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) { + function _msgSender() internal view virtual override(Context, ERC2771Context, Multicall) returns (address sender) { return ERC2771Context._msgSender(); } diff --git a/contracts/infra/TWStatelessFactory.sol b/contracts/infra/TWStatelessFactory.sol index 7db3b5684..43ee5c60d 100644 --- a/contracts/infra/TWStatelessFactory.sol +++ b/contracts/infra/TWStatelessFactory.sol @@ -41,11 +41,7 @@ contract TWStatelessFactory is Multicall, ERC2771Context, IContractFactory { } } - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() internal view virtual override(Multicall, ERC2771Context) returns (address sender) { return ERC2771Context._msgSender(); } - - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771Context._msgData(); - } } diff --git a/contracts/infra/registry/entrypoint/TWMultichainRegistryRouter.sol b/contracts/infra/registry/entrypoint/TWMultichainRegistryRouter.sol index 03c88e335..0075ca570 100644 --- a/contracts/infra/registry/entrypoint/TWMultichainRegistryRouter.sol +++ b/contracts/infra/registry/entrypoint/TWMultichainRegistryRouter.sol @@ -51,22 +51,11 @@ contract TWMultichainRegistryRouter is PermissionsEnumerableLogic, ERC2771Contex return hasRole(DEFAULT_ADMIN_ROLE, _msgSender()); } - function _msgSender() internal view override(ERC2771ContextLogic, PermissionsLogic) returns (address sender) { - if (isTrustedForwarder(msg.sender)) { - // The assembly code is more direct than the Solidity version using `abi.decode`. - assembly { - sender := shr(96, calldataload(sub(calldatasize(), 20))) - } - } else { - return msg.sender; - } + function _msgSender() internal view override(ERC2771ContextLogic, PermissionsLogic, Multicall) returns (address) { + return ERC2771ContextLogic._msgSender(); } function _msgData() internal view override(ERC2771ContextLogic, PermissionsLogic) returns (bytes calldata) { - if (isTrustedForwarder(msg.sender)) { - return msg.data[:msg.data.length - 20]; - } else { - return msg.data; - } + return ERC2771ContextLogic._msgData(); } } diff --git a/contracts/legacy-contracts/pre-builts/DropERC1155_V2.sol b/contracts/legacy-contracts/pre-builts/DropERC1155_V2.sol index a02fcdede..2d8f04971 100644 --- a/contracts/legacy-contracts/pre-builts/DropERC1155_V2.sol +++ b/contracts/legacy-contracts/pre-builts/DropERC1155_V2.sol @@ -713,7 +713,7 @@ contract DropERC1155_V2 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/legacy-contracts/pre-builts/DropERC20_V2.sol b/contracts/legacy-contracts/pre-builts/DropERC20_V2.sol index 2b36a8632..72310658a 100644 --- a/contracts/legacy-contracts/pre-builts/DropERC20_V2.sol +++ b/contracts/legacy-contracts/pre-builts/DropERC20_V2.sol @@ -503,7 +503,7 @@ contract DropERC20_V2 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/legacy-contracts/pre-builts/DropERC721_V3.sol b/contracts/legacy-contracts/pre-builts/DropERC721_V3.sol index d8d0afff4..2c7534d6e 100644 --- a/contracts/legacy-contracts/pre-builts/DropERC721_V3.sol +++ b/contracts/legacy-contracts/pre-builts/DropERC721_V3.sol @@ -727,7 +727,7 @@ contract DropERC721_V3 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/legacy-contracts/pre-builts/SignatureDrop_V4.sol b/contracts/legacy-contracts/pre-builts/SignatureDrop_V4.sol index a49c089cc..4b3e313bc 100644 --- a/contracts/legacy-contracts/pre-builts/SignatureDrop_V4.sol +++ b/contracts/legacy-contracts/pre-builts/SignatureDrop_V4.sol @@ -342,7 +342,7 @@ contract SignatureDrop_V4 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/lib/Address.sol b/contracts/lib/Address.sol index 41c55c188..bd1e6ce2f 100644 --- a/contracts/lib/Address.sol +++ b/contracts/lib/Address.sol @@ -1,25 +1,66 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; +pragma solidity ^0.8.1; -/// @author thirdweb +/// @author thirdweb, OpenZeppelin Contracts (v4.9.0) +/** + * @dev Collection of functions related to the address type + */ library Address { /** - * @dev Returns whether an address is a smart contract. + * @dev Returns true if `account` is a contract. * - * `account` MAY NOT be a smart contract when this function returns `true` - * Other than EOAs, `isContract` will return `false` for: + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed - * - a contract in construction (since the code is only stored at the end of - * the constructor execution) + * + * Furthermore, `isContract` will also return true if the target contract within + * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, + * which only has an effect at the end of a transaction. + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== */ function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + return account.code.length > 0; } - /// @dev Sends `amount` of wei to `recipient`. + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); @@ -27,13 +68,34 @@ library Address { require(success, "Address: unable to send value, recipient may have reverted"); } - /// @dev Performs a low-level call on `target` with `data`. + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCall(target, data, "Address: low-level call failed"); + return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } - /// @dev Performs a call on `target` with `data`, with `errorMessage` as a fallback - /// revert reason when `target` reverts. + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ function functionCall( address target, bytes memory data, @@ -42,13 +104,27 @@ library Address { return functionCallWithValue(target, data, 0, errorMessage); } - /// @dev Performs a low-level call on `target` with `data` and `value`. + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } - /// @dev Performs a static call on `target` with `data` and `value`, with `errorMessage` as a fallback - /// revert reason when `target` reverts. + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ function functionCallWithValue( address target, bytes memory data, @@ -56,49 +132,90 @@ library Address { string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); - require(isContract(target) && !isContract(msg.sender), "Address: invalid call"); - (bool success, bytes memory returndata) = target.call{ value: value }(data); - return verifyCallResult(success, returndata, errorMessage); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); } - /// @dev Performs a static call on `target` with `data`. + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } - /// @dev Performs a static call on `target` with `data`, with `errorMessage` as a fallback - /// revert reason when `target` reverts. + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { - require(isContract(target) && !isContract(msg.sender), "Address: invalid static call"); - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResult(success, returndata, errorMessage); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); } - /// @dev Performs a delegate call on `target` with `data`. + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } - /// @dev Performs a delegate call on `target` with `data`, with `errorMessage` as a fallback - /// revert reason when `target` reverts. + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { - require(isContract(target) && !isContract(msg.sender), "Address: invalid delegate call"); - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResult(success, returndata, errorMessage); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling + * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. + * + * _Available since v4.8._ + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata, + string memory errorMessage + ) internal view returns (bytes memory) { + if (success) { + if (returndata.length == 0) { + // only check isContract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + require(isContract(target), "Address: call to non-contract"); + } + return returndata; + } else { + _revert(returndata, errorMessage); + } } - /// @dev Verifies that a low level call was successful. + /** + * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason or using the provided one. + * + * _Available since v4.3._ + */ function verifyCallResult( bool success, bytes memory returndata, @@ -107,17 +224,21 @@ library Address { if (success) { return returndata; } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); + _revert(returndata, errorMessage); + } + } + + function _revert(bytes memory returndata, string memory errorMessage) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) } + } else { + revert(errorMessage); } } } diff --git a/contracts/prebuilts/account/dynamic/DynamicAccountFactory.sol b/contracts/prebuilts/account/dynamic/DynamicAccountFactory.sol index 864091a6b..a40642c00 100644 --- a/contracts/prebuilts/account/dynamic/DynamicAccountFactory.sol +++ b/contracts/prebuilts/account/dynamic/DynamicAccountFactory.sol @@ -53,4 +53,9 @@ contract DynamicAccountFactory is BaseAccountFactory, ContractMetadata, Permissi function _canSetContractURI() internal view virtual override returns (bool) { return hasRole(DEFAULT_ADMIN_ROLE, msg.sender); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Permissions) returns (address) { + return msg.sender; + } } diff --git a/contracts/prebuilts/account/managed/ManagedAccountFactory.sol b/contracts/prebuilts/account/managed/ManagedAccountFactory.sol index 70bfd832e..c5f51e3ca 100644 --- a/contracts/prebuilts/account/managed/ManagedAccountFactory.sol +++ b/contracts/prebuilts/account/managed/ManagedAccountFactory.sol @@ -60,4 +60,9 @@ contract ManagedAccountFactory is BaseAccountFactory, ContractMetadata, Permissi function _canSetContractURI() internal view virtual override returns (bool) { return hasRole(DEFAULT_ADMIN_ROLE, msg.sender); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Permissions) returns (address) { + return msg.sender; + } } diff --git a/contracts/prebuilts/account/non-upgradeable/AccountFactory.sol b/contracts/prebuilts/account/non-upgradeable/AccountFactory.sol index 017b2711a..f3544d97a 100644 --- a/contracts/prebuilts/account/non-upgradeable/AccountFactory.sol +++ b/contracts/prebuilts/account/non-upgradeable/AccountFactory.sol @@ -50,4 +50,9 @@ contract AccountFactory is BaseAccountFactory, ContractMetadata, PermissionsEnum function _canSetContractURI() internal view virtual override returns (bool) { return hasRole(DEFAULT_ADMIN_ROLE, msg.sender); } + + /// @notice Returns the sender in the given execution context. + function _msgSender() internal view override(Multicall, Permissions) returns (address) { + return msg.sender; + } } diff --git a/contracts/prebuilts/drop/DropERC1155.sol b/contracts/prebuilts/drop/DropERC1155.sol index 99748d52d..4d4e12a9f 100644 --- a/contracts/prebuilts/drop/DropERC1155.sol +++ b/contracts/prebuilts/drop/DropERC1155.sol @@ -362,7 +362,7 @@ contract DropERC1155 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/drop/DropERC20.sol b/contracts/prebuilts/drop/DropERC20.sol index b8ec7f748..859931a89 100644 --- a/contracts/prebuilts/drop/DropERC20.sol +++ b/contracts/prebuilts/drop/DropERC20.sol @@ -234,7 +234,7 @@ contract DropERC20 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/drop/DropERC721.sol b/contracts/prebuilts/drop/DropERC721.sol index 96a233520..5c6738636 100644 --- a/contracts/prebuilts/drop/DropERC721.sol +++ b/contracts/prebuilts/drop/DropERC721.sol @@ -371,7 +371,7 @@ contract DropERC721 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/loyalty/LoyaltyCard.sol b/contracts/prebuilts/loyalty/LoyaltyCard.sol index a796855e0..7f986a352 100644 --- a/contracts/prebuilts/loyalty/LoyaltyCard.sol +++ b/contracts/prebuilts/loyalty/LoyaltyCard.sol @@ -307,7 +307,7 @@ contract LoyaltyCard is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/marketplace-legacy/Marketplace.sol b/contracts/prebuilts/marketplace-legacy/Marketplace.sol index ba136e58a..0a7ea2301 100644 --- a/contracts/prebuilts/marketplace-legacy/Marketplace.sol +++ b/contracts/prebuilts/marketplace-legacy/Marketplace.sol @@ -889,7 +889,7 @@ contract Marketplace is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/marketplace/entrypoint/MarketplaceV3.sol b/contracts/prebuilts/marketplace/entrypoint/MarketplaceV3.sol index cfa4939a4..5d11d8796 100644 --- a/contracts/prebuilts/marketplace/entrypoint/MarketplaceV3.sol +++ b/contracts/prebuilts/marketplace/entrypoint/MarketplaceV3.sol @@ -167,7 +167,12 @@ contract MarketplaceV3 is return _hasRole(EXTENSION_ROLE, msg.sender); } - function _msgSender() internal view override(ERC2771ContextUpgradeable, Permissions) returns (address sender) { + function _msgSender() + internal + view + override(ERC2771ContextUpgradeable, Permissions, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } diff --git a/contracts/prebuilts/multiwrap/Multiwrap.sol b/contracts/prebuilts/multiwrap/Multiwrap.sol index 0a6d59d6f..e074ba1d8 100644 --- a/contracts/prebuilts/multiwrap/Multiwrap.sol +++ b/contracts/prebuilts/multiwrap/Multiwrap.sol @@ -246,7 +246,7 @@ contract Multiwrap is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/open-edition/OpenEditionERC721.sol b/contracts/prebuilts/open-edition/OpenEditionERC721.sol index 3b7287ab8..f802b2f14 100644 --- a/contracts/prebuilts/open-edition/OpenEditionERC721.sol +++ b/contracts/prebuilts/open-edition/OpenEditionERC721.sol @@ -256,11 +256,13 @@ contract OpenEditionERC721 is return _msgSender(); } - function _msgSender() internal view virtual override(ERC2771ContextUpgradeable) returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - function _msgData() internal view virtual override(ERC2771ContextUpgradeable) returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/pack/Pack.sol b/contracts/prebuilts/pack/Pack.sol index 956459cf2..0b427bc75 100644 --- a/contracts/prebuilts/pack/Pack.sol +++ b/contracts/prebuilts/pack/Pack.sol @@ -469,7 +469,7 @@ contract Pack is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/pack/PackVRFDirect.sol b/contracts/prebuilts/pack/PackVRFDirect.sol index d0c17fbde..541ceb904 100644 --- a/contracts/prebuilts/pack/PackVRFDirect.sol +++ b/contracts/prebuilts/pack/PackVRFDirect.sol @@ -497,7 +497,7 @@ contract PackVRFDirect is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/signature-drop/SignatureDrop.sol b/contracts/prebuilts/signature-drop/SignatureDrop.sol index b601aed2d..b505940bb 100644 --- a/contracts/prebuilts/signature-drop/SignatureDrop.sol +++ b/contracts/prebuilts/signature-drop/SignatureDrop.sol @@ -353,7 +353,7 @@ contract SignatureDrop is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/split/Split.sol b/contracts/prebuilts/split/Split.sol index 96d539fc6..69bc32bcd 100644 --- a/contracts/prebuilts/split/Split.sol +++ b/contracts/prebuilts/split/Split.sol @@ -156,7 +156,7 @@ contract Split is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/staking/EditionStake.sol b/contracts/prebuilts/staking/EditionStake.sol index 35fc483f9..67f8aea42 100644 --- a/contracts/prebuilts/staking/EditionStake.sol +++ b/contracts/prebuilts/staking/EditionStake.sol @@ -199,11 +199,13 @@ contract EditionStake is return _msgSender(); } - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/staking/NFTStake.sol b/contracts/prebuilts/staking/NFTStake.sol index df6c6b3a1..32d5a6f9d 100644 --- a/contracts/prebuilts/staking/NFTStake.sol +++ b/contracts/prebuilts/staking/NFTStake.sol @@ -189,11 +189,13 @@ contract NFTStake is return _msgSender(); } - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/staking/TokenStake.sol b/contracts/prebuilts/staking/TokenStake.sol index 5d6c5f888..d908db791 100644 --- a/contracts/prebuilts/staking/TokenStake.sol +++ b/contracts/prebuilts/staking/TokenStake.sol @@ -185,11 +185,13 @@ contract TokenStake is return _msgSender(); } - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/tiered-drop/TieredDrop.sol b/contracts/prebuilts/tiered-drop/TieredDrop.sol index 9d5571ca5..f52c82b17 100644 --- a/contracts/prebuilts/tiered-drop/TieredDrop.sol +++ b/contracts/prebuilts/tiered-drop/TieredDrop.sol @@ -589,7 +589,7 @@ contract TieredDrop is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/token/TokenERC1155.sol b/contracts/prebuilts/token/TokenERC1155.sol index a3db15910..fd2375eed 100644 --- a/contracts/prebuilts/token/TokenERC1155.sol +++ b/contracts/prebuilts/token/TokenERC1155.sol @@ -544,7 +544,7 @@ contract TokenERC1155 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/token/TokenERC20.sol b/contracts/prebuilts/token/TokenERC20.sol index 9fe640ce4..4ff5898b1 100644 --- a/contracts/prebuilts/token/TokenERC20.sol +++ b/contracts/prebuilts/token/TokenERC20.sol @@ -281,7 +281,7 @@ contract TokenERC20 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/token/TokenERC721.sol b/contracts/prebuilts/token/TokenERC721.sol index 4cfd93a73..13f977c6b 100644 --- a/contracts/prebuilts/token/TokenERC721.sol +++ b/contracts/prebuilts/token/TokenERC721.sol @@ -437,7 +437,7 @@ contract TokenERC721 is internal view virtual - override(ContextUpgradeable, ERC2771ContextUpgradeable) + override(ContextUpgradeable, ERC2771ContextUpgradeable, Multicall) returns (address sender) { return ERC2771ContextUpgradeable._msgSender(); diff --git a/contracts/prebuilts/unaudited/airdrop/AirdropERC1155.sol b/contracts/prebuilts/unaudited/airdrop/AirdropERC1155.sol index a07588a7a..b27a0afcc 100644 --- a/contracts/prebuilts/unaudited/airdrop/AirdropERC1155.sol +++ b/contracts/prebuilts/unaudited/airdrop/AirdropERC1155.sol @@ -142,12 +142,13 @@ contract AirdropERC1155 is } /// @dev See ERC2771 - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - /// @dev See ERC2771 - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/unaudited/airdrop/AirdropERC1155Claimable.sol b/contracts/prebuilts/unaudited/airdrop/AirdropERC1155Claimable.sol index a7bd4966f..7d42b469a 100644 --- a/contracts/prebuilts/unaudited/airdrop/AirdropERC1155Claimable.sol +++ b/contracts/prebuilts/unaudited/airdrop/AirdropERC1155Claimable.sol @@ -187,11 +187,13 @@ contract AirdropERC1155Claimable is Miscellaneous //////////////////////////////////////////////////////////////*/ - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/unaudited/airdrop/AirdropERC20.sol b/contracts/prebuilts/unaudited/airdrop/AirdropERC20.sol index cf412467d..b5829896f 100644 --- a/contracts/prebuilts/unaudited/airdrop/AirdropERC20.sol +++ b/contracts/prebuilts/unaudited/airdrop/AirdropERC20.sol @@ -182,12 +182,13 @@ contract AirdropERC20 is } /// @dev See ERC2771 - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - /// @dev See ERC2771 - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/unaudited/airdrop/AirdropERC20Claimable.sol b/contracts/prebuilts/unaudited/airdrop/AirdropERC20Claimable.sol index a247ece6e..22840d247 100644 --- a/contracts/prebuilts/unaudited/airdrop/AirdropERC20Claimable.sol +++ b/contracts/prebuilts/unaudited/airdrop/AirdropERC20Claimable.sol @@ -168,11 +168,13 @@ contract AirdropERC20Claimable is Miscellaneous //////////////////////////////////////////////////////////////*/ - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/unaudited/airdrop/AirdropERC721.sol b/contracts/prebuilts/unaudited/airdrop/AirdropERC721.sol index c36c2bb3d..97a99f63f 100644 --- a/contracts/prebuilts/unaudited/airdrop/AirdropERC721.sol +++ b/contracts/prebuilts/unaudited/airdrop/AirdropERC721.sol @@ -131,12 +131,13 @@ contract AirdropERC721 is } /// @dev See ERC2771 - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - /// @dev See ERC2771 - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/unaudited/airdrop/AirdropERC721Claimable.sol b/contracts/prebuilts/unaudited/airdrop/AirdropERC721Claimable.sol index a76f08225..16f38d3b7 100644 --- a/contracts/prebuilts/unaudited/airdrop/AirdropERC721Claimable.sol +++ b/contracts/prebuilts/unaudited/airdrop/AirdropERC721Claimable.sol @@ -185,11 +185,13 @@ contract AirdropERC721Claimable is Miscellaneous //////////////////////////////////////////////////////////////*/ - function _msgSender() internal view virtual override returns (address sender) { + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { return ERC2771ContextUpgradeable._msgSender(); } - - function _msgData() internal view virtual override returns (bytes calldata) { - return ERC2771ContextUpgradeable._msgData(); - } } diff --git a/contracts/prebuilts/unaudited/burn-to-claim-drop/BurnToClaimDropERC721.sol b/contracts/prebuilts/unaudited/burn-to-claim-drop/BurnToClaimDropERC721.sol index 59b50ee93..9346c1ff3 100644 --- a/contracts/prebuilts/unaudited/burn-to-claim-drop/BurnToClaimDropERC721.sol +++ b/contracts/prebuilts/unaudited/burn-to-claim-drop/BurnToClaimDropERC721.sol @@ -123,4 +123,15 @@ contract BurnToClaimDropERC721 is PermissionsStorage.Data storage data = PermissionsStorage.data(); return data._hasRole[role][addr]; } + + /// @notice Returns the sender in the given execution context. + function _msgSender() + internal + view + virtual + override(ERC2771ContextUpgradeable, Multicall) + returns (address sender) + { + return ERC2771ContextUpgradeable._msgSender(); + } } diff --git a/src/test/Multicall.t.sol b/src/test/Multicall.t.sol new file mode 100644 index 000000000..3b9bbfb93 --- /dev/null +++ b/src/test/Multicall.t.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "@std/Test.sol"; + +import { Multicall } from "contracts/extension/Multicall.sol"; +import { Forwarder } from "contracts/infra/forwarder/Forwarder.sol"; +import { ERC2771Context } from "contracts/extension/upgradeable/ERC2771Context.sol"; +import { TokenERC721 } from "contracts/prebuilts/token/TokenERC721.sol"; +import { Strings } from "contracts/lib/Strings.sol"; +import { TWProxy } from "contracts/infra/TWProxy.sol"; + +contract MockMulticallForwarderConsumer is Multicall, ERC2771Context { + event Increment(address caller); + mapping(address => uint256) public counter; + + constructor(address[] memory trustedForwarders) ERC2771Context(trustedForwarders) {} + + function increment() external { + counter[_msgSender()]++; + emit Increment(_msgSender()); + } + + function _msgSender() internal view override(Multicall, ERC2771Context) returns (address sender) { + return ERC2771Context._msgSender(); + } +} + +contract MulticallTest is Test { + // Target (mock) contract + address internal consumer; + TokenERC721 internal token; + + address internal user1; + uint256 internal user1Pkey = 100; + + address internal user2; + uint256 internal user2Pkey = 200; + + // Forwarder details + Forwarder internal forwarder; + + bytes32 internal typehashForwardRequest; + bytes32 internal nameHash; + bytes32 internal versionHash; + + bytes32 internal typehashEip712; + bytes32 internal domainSeparator; + + function setUp() public { + user1 = vm.addr(user1Pkey); + user2 = vm.addr(user2Pkey); + + // Deploy forwarder + forwarder = new Forwarder(); + + // Deploy consumer + address[] memory forwarders = new address[](1); + forwarders[0] = address(forwarder); + consumer = address(new MockMulticallForwarderConsumer(forwarders)); + + // Deploy `TokenERC721` + address impl = address(new TokenERC721()); + token = TokenERC721( + address( + new TWProxy( + impl, + abi.encodeWithSelector( + TokenERC721.initialize.selector, + user1, + "name", + "SYMBOL", + "ipfs://", + forwarders, + user1, + user1, + 0, + 0, + user1 + ) + ) + ) + ); + + // Setup forwarder details + typehashForwardRequest = keccak256( + "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)" + ); + nameHash = keccak256(bytes("GSNv2 Forwarder")); + versionHash = keccak256(bytes("0.0.1")); + typehashEip712 = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + domainSeparator = keccak256( + abi.encode(typehashEip712, nameHash, versionHash, block.chainid, address(forwarder)) + ); + + vm.label(user1, "USER_1"); + vm.label(user2, "USER_2"); + vm.label(address(forwarder), "FORWARDER"); + vm.label(address(consumer), "CONSUMER"); + } + + function _signForwarderRequest( + Forwarder.ForwardRequest memory forwardRequest, + uint256 privateKey + ) internal view returns (bytes memory) { + bytes memory encodedRequest = abi.encode( + typehashForwardRequest, + forwardRequest.from, + forwardRequest.to, + forwardRequest.value, + forwardRequest.gas, + forwardRequest.nonce, + keccak256(forwardRequest.data) + ); + bytes32 structHash = keccak256(encodedRequest); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash); + bytes memory signature = abi.encodePacked(r, s, v); + + return signature; + } + + function test_multicall_viaDirectCall() public { + // Make 3 calls to `increment` within a multicall + bytes[] memory calls = new bytes[](3); + calls[0] = abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector); + calls[1] = abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector); + calls[2] = abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector); + + // CASE 1: multicall without using forwarder. Should increment counter for the caller i.e. `msg.sender`. + + assertEq(MockMulticallForwarderConsumer(consumer).counter(user1), 0); + + vm.prank(user1); + Multicall(consumer).multicall(calls); + + assertEq(MockMulticallForwarderConsumer(consumer).counter(user1), 3); // counter incremented! + } + + function test_multicall_viaForwarder() public { + // Make 3 calls to `increment` within a multicall + bytes[] memory calls = new bytes[](3); + calls[0] = abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector); + calls[1] = abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector); + calls[2] = abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector); + + // CASE 2: multicall with using forwarder. Should increment counter for the signer of the forwarder request. + + bytes memory multicallData = abi.encodeWithSelector(Multicall.multicall.selector, calls); + + Forwarder.ForwardRequest memory forwardRequest; + + forwardRequest.from = user1; + forwardRequest.to = address(consumer); + forwardRequest.value = 0; + forwardRequest.gas = 100_000; + forwardRequest.nonce = Forwarder(forwarder).getNonce(user1); + forwardRequest.data = multicallData; + + bytes memory signature = _signForwarderRequest(forwardRequest, user1Pkey); + + Forwarder(forwarder).execute(forwardRequest, signature); + + assertEq(MockMulticallForwarderConsumer(consumer).counter(user1), 3); // counter incremented! + } + + function test_multicall_viaForwarder_attemptSpoof() public { + // Make 3 calls to `increment` within a multicall + bytes[] memory callsSpoof = new bytes[](3); + callsSpoof[0] = abi.encodePacked( + abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector), + user1 + ); + callsSpoof[1] = abi.encodePacked( + abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector), + user1 + ); + callsSpoof[2] = abi.encodePacked( + abi.encodeWithSelector(MockMulticallForwarderConsumer.increment.selector), + user1 + ); + + // CASE 3: attempting to spoof address by manually appending address to multicall data arg. + // + // This attempt fails because `multicall` enforces original forwarder request signer + // as the `_msgSender()`. + + bytes memory multicallDataSpoof = abi.encodeWithSelector(Multicall.multicall.selector, callsSpoof); + + // user2 spoofing as user1 + Forwarder.ForwardRequest memory forwardRequestSpoof; + + forwardRequestSpoof.from = user2; + forwardRequestSpoof.to = address(consumer); + forwardRequestSpoof.value = 0; + forwardRequestSpoof.gas = 100_000; + forwardRequestSpoof.nonce = Forwarder(forwarder).getNonce(user2); + forwardRequestSpoof.data = multicallDataSpoof; + + bytes memory signatureSpoof = _signForwarderRequest(forwardRequestSpoof, user2Pkey); + + // vm.expectRevert(); + Forwarder(forwarder).execute(forwardRequestSpoof, signatureSpoof); + + assertEq(MockMulticallForwarderConsumer(consumer).counter(user1), 0); // counter unchanged! + assertEq(MockMulticallForwarderConsumer(consumer).counter(user2), 3); // counter incremented for forwarder request signer! + } + + function test_multicall_tokenerc721_viaForwarder_attemptSpoof() public { + // User1 is admin on `token` + assertTrue(token.hasRole(keccak256("MINTER_ROLE"), user1)); + + // token ID `0` has no owner + vm.expectRevert("ERC721: invalid token ID"); + token.ownerOf(0); + + // Make call to `mintTo` within a multicall + bytes[] memory callsSpoof = new bytes[](1); + callsSpoof[0] = abi.encodePacked( + abi.encodeWithSelector(TokenERC721.mintTo.selector, user2, "metadataURI"), + user1 + ); + // CASE: attempting to spoof address by manually appending address to multicall data arg. + // + // This attempt fails because `multicall` enforces original forwarder request signer + // as the `_msgSender()`. + + bytes memory multicallDataSpoof = abi.encodeWithSelector(Multicall.multicall.selector, callsSpoof); + + // user2 spoofing as user1 + Forwarder.ForwardRequest memory forwardRequestSpoof; + + forwardRequestSpoof.from = user2; + forwardRequestSpoof.to = address(token); + forwardRequestSpoof.value = 0; + forwardRequestSpoof.gas = 100_000; + forwardRequestSpoof.nonce = Forwarder(forwarder).getNonce(user2); + forwardRequestSpoof.data = multicallDataSpoof; + + bytes memory signatureSpoof = _signForwarderRequest(forwardRequestSpoof, user2Pkey); + + // Minter role check occurs on user2 i.e. signer of the forwarder request, and not user1 i.e. the address user2 attempts to spoof. + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(uint160(user2), 20), + " is missing role ", + Strings.toHexString(uint256(keccak256("MINTER_ROLE")), 32) + ) + ); + Forwarder(forwarder).execute(forwardRequestSpoof, signatureSpoof); + + // token ID `0` still has no owner + vm.expectRevert("ERC721: invalid token ID"); + token.ownerOf(0); + } +}