From ccffe671054676b65a5d7f3434eb75b3237ff3dc Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sun, 21 Aug 2022 22:26:03 -0700 Subject: [PATCH 01/33] Add _msgSender() trick Fixes #132 Enables MetaTransaction facets and [Delegatable](https://github.com/delegatable/delegatable-sol/blob/main/contracts/diamond/README.md). --- contracts/access/ownable/OwnableInternal.sol | 26 ++++++++-- contracts/security/PausableInternal.sol | 24 +++++++++- contracts/token/ERC1155/base/ERC1155Base.sol | 14 +++--- .../ERC1155/base/ERC1155BaseInternal.sol | 47 +++++++++++++++---- contracts/token/ERC20/base/ERC20Base.sol | 4 +- .../token/ERC20/base/ERC20BaseInternal.sol | 24 +++++++++- contracts/token/ERC721/base/ERC721Base.sol | 12 ++--- .../token/ERC721/base/ERC721BaseInternal.sol | 22 ++++++++- 8 files changed, 140 insertions(+), 33 deletions(-) diff --git a/contracts/access/ownable/OwnableInternal.sol b/contracts/access/ownable/OwnableInternal.sol index 49366e812..1b3bca8a6 100644 --- a/contracts/access/ownable/OwnableInternal.sol +++ b/contracts/access/ownable/OwnableInternal.sol @@ -12,13 +12,13 @@ abstract contract OwnableInternal is IOwnableInternal { using OwnableStorage for OwnableStorage.Layout; modifier onlyOwner() { - require(msg.sender == _owner(), 'Ownable: sender must be owner'); + require(_msgSender() == _owner(), 'Ownable: sender must be owner'); _; } modifier onlyTransitiveOwner() { require( - msg.sender == _transitiveOwner(), + _msgSender() == _transitiveOwner(), 'Ownable: sender must be transitive owner' ); _; @@ -44,6 +44,26 @@ abstract contract OwnableInternal is IOwnableInternal { function _transferOwnership(address account) internal virtual { OwnableStorage.layout().setOwner(account); - emit OwnershipTransferred(msg.sender, account); + emit OwnershipTransferred(_msgSender(), account); + } + + /* + * @notice Overrides the msgSender to enable delegation message signing. + * @returns address - The account whose authority is being acted on. + */ + function _msgSender() internal view virtual returns (address sender) { + if (msg.sender == address(this)) { + bytes memory array = msg.data; + uint256 index = msg.data.length; + assembly { + sender := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } else { + sender = msg.sender; + } + return sender; } } diff --git a/contracts/security/PausableInternal.sol b/contracts/security/PausableInternal.sol index 3aec4f662..50e9077b9 100644 --- a/contracts/security/PausableInternal.sol +++ b/contracts/security/PausableInternal.sol @@ -37,7 +37,7 @@ abstract contract PausableInternal { */ function _pause() internal virtual whenNotPaused { PausableStorage.layout().paused = true; - emit Paused(msg.sender); + emit Paused(_msgSender()); } /** @@ -45,6 +45,26 @@ abstract contract PausableInternal { */ function _unpause() internal virtual whenPaused { PausableStorage.layout().paused = false; - emit Unpaused(msg.sender); + emit Unpaused(_msgSender()); + } + + /* + * @notice Overrides the msgSender to enable delegation message signing. + * @returns address - The account whose authority is being acted on. + */ + function _msgSender() internal view virtual returns (address sender) { + if (msg.sender == address(this)) { + bytes memory array = msg.data; + uint256 index = msg.data.length; + assembly { + sender := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } else { + sender = msg.sender; + } + return sender; } } diff --git a/contracts/token/ERC1155/base/ERC1155Base.sol b/contracts/token/ERC1155/base/ERC1155Base.sol index 1df879a1f..7356db720 100644 --- a/contracts/token/ERC1155/base/ERC1155Base.sol +++ b/contracts/token/ERC1155/base/ERC1155Base.sol @@ -73,13 +73,13 @@ abstract contract ERC1155Base is IERC1155Base, ERC1155BaseInternal { */ function setApprovalForAll(address operator, bool status) public virtual { require( - msg.sender != operator, + _msgSender() != operator, 'ERC1155: setting approval status for self' ); - ERC1155BaseStorage.layout().operatorApprovals[msg.sender][ + ERC1155BaseStorage.layout().operatorApprovals[_msgSender()][ operator ] = status; - emit ApprovalForAll(msg.sender, operator, status); + emit ApprovalForAll(_msgSender(), operator, status); } /** @@ -93,10 +93,10 @@ abstract contract ERC1155Base is IERC1155Base, ERC1155BaseInternal { bytes memory data ) public virtual { require( - from == msg.sender || isApprovedForAll(from, msg.sender), + from == _msgSender() || isApprovedForAll(from, _msgSender()), 'ERC1155: caller is not owner nor approved' ); - _safeTransfer(msg.sender, from, to, id, amount, data); + _safeTransfer(_msgSender(), from, to, id, amount, data); } /** @@ -110,9 +110,9 @@ abstract contract ERC1155Base is IERC1155Base, ERC1155BaseInternal { bytes memory data ) public virtual { require( - from == msg.sender || isApprovedForAll(from, msg.sender), + from == _msgSender() || isApprovedForAll(from, _msgSender()), 'ERC1155: caller is not owner nor approved' ); - _safeTransferBatch(msg.sender, from, to, ids, amounts, data); + _safeTransferBatch(_msgSender(), from, to, ids, amounts, data); } } diff --git a/contracts/token/ERC1155/base/ERC1155BaseInternal.sol b/contracts/token/ERC1155/base/ERC1155BaseInternal.sol index 639442757..71e9bee02 100644 --- a/contracts/token/ERC1155/base/ERC1155BaseInternal.sol +++ b/contracts/token/ERC1155/base/ERC1155BaseInternal.sol @@ -50,7 +50,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { require(account != address(0), 'ERC1155: mint to the zero address'); _beforeTokenTransfer( - msg.sender, + _msgSender(), address(0), account, _asSingletonArray(id), @@ -60,7 +60,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { ERC1155BaseStorage.layout().balances[id][account] += amount; - emit TransferSingle(msg.sender, address(0), account, id, amount); + emit TransferSingle(_msgSender(), address(0), account, id, amount); } /** @@ -79,7 +79,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { _mint(account, id, amount, data); _doSafeTransferAcceptanceCheck( - msg.sender, + _msgSender(), address(0), account, id, @@ -109,7 +109,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { ); _beforeTokenTransfer( - msg.sender, + _msgSender(), address(0), account, ids, @@ -127,7 +127,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { } } - emit TransferBatch(msg.sender, address(0), account, ids, amounts); + emit TransferBatch(_msgSender(), address(0), account, ids, amounts); } /** @@ -146,7 +146,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { _mintBatch(account, ids, amounts, data); _doSafeBatchTransferAcceptanceCheck( - msg.sender, + _msgSender(), address(0), account, ids, @@ -169,7 +169,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { require(account != address(0), 'ERC1155: burn from the zero address'); _beforeTokenTransfer( - msg.sender, + _msgSender(), account, address(0), _asSingletonArray(id), @@ -189,7 +189,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { balances[account] -= amount; } - emit TransferSingle(msg.sender, account, address(0), id, amount); + emit TransferSingle(_msgSender(), account, address(0), id, amount); } /** @@ -209,7 +209,14 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { 'ERC1155: ids and amounts length mismatch' ); - _beforeTokenTransfer(msg.sender, account, address(0), ids, amounts, ''); + _beforeTokenTransfer( + _msgSender(), + account, + address(0), + ids, + amounts, + '' + ); mapping(uint256 => mapping(address => uint256)) storage balances = ERC1155BaseStorage.layout().balances; @@ -225,7 +232,7 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { } } - emit TransferBatch(msg.sender, account, address(0), ids, amounts); + emit TransferBatch(_msgSender(), account, address(0), ids, amounts); } /** @@ -504,4 +511,24 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { uint256[] memory amounts, bytes memory data ) internal virtual {} + + /* + * @notice Overrides the msgSender to enable delegation message signing. + * @returns address - The account whose authority is being acted on. + */ + function _msgSender() internal view virtual returns (address sender) { + if (msg.sender == address(this)) { + bytes memory array = msg.data; + uint256 index = msg.data.length; + assembly { + sender := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } else { + sender = msg.sender; + } + return sender; + } } diff --git a/contracts/token/ERC20/base/ERC20Base.sol b/contracts/token/ERC20/base/ERC20Base.sol index 35d003289..b17d88cbc 100644 --- a/contracts/token/ERC20/base/ERC20Base.sol +++ b/contracts/token/ERC20/base/ERC20Base.sol @@ -45,7 +45,7 @@ abstract contract ERC20Base is IERC20Base, ERC20BaseInternal { virtual returns (bool) { - return _approve(msg.sender, spender, amount); + return _approve(_msgSender(), spender, amount); } /** @@ -56,7 +56,7 @@ abstract contract ERC20Base is IERC20Base, ERC20BaseInternal { virtual returns (bool) { - return _transfer(msg.sender, recipient, amount); + return _transfer(_msgSender(), recipient, amount); } /** diff --git a/contracts/token/ERC20/base/ERC20BaseInternal.sol b/contracts/token/ERC20/base/ERC20BaseInternal.sol index e3aace4d3..0f38c5ce3 100644 --- a/contracts/token/ERC20/base/ERC20BaseInternal.sol +++ b/contracts/token/ERC20/base/ERC20BaseInternal.sol @@ -151,7 +151,7 @@ abstract contract ERC20BaseInternal is IERC20BaseInternal { address recipient, uint256 amount ) internal virtual returns (bool) { - uint256 currentAllowance = _allowance(holder, msg.sender); + uint256 currentAllowance = _allowance(holder, _msgSender()); require( currentAllowance >= amount, @@ -159,7 +159,7 @@ abstract contract ERC20BaseInternal is IERC20BaseInternal { ); unchecked { - _approve(holder, msg.sender, currentAllowance - amount); + _approve(holder, _msgSender(), currentAllowance - amount); } _transfer(holder, recipient, amount); @@ -179,4 +179,24 @@ abstract contract ERC20BaseInternal is IERC20BaseInternal { address to, uint256 amount ) internal virtual {} + + /* + * @notice Overrides the msgSender to enable delegation message signing. + * @returns address - The account whose authority is being acted on. + */ + function _msgSender() internal view virtual returns (address sender) { + if (msg.sender == address(this)) { + bytes memory array = msg.data; + uint256 index = msg.data.length; + assembly { + sender := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } else { + sender = msg.sender; + } + return sender; + } } diff --git a/contracts/token/ERC721/base/ERC721Base.sol b/contracts/token/ERC721/base/ERC721Base.sol index 22fe73421..343c61c98 100644 --- a/contracts/token/ERC721/base/ERC721Base.sol +++ b/contracts/token/ERC721/base/ERC721Base.sol @@ -61,7 +61,7 @@ abstract contract ERC721Base is IERC721Base, ERC721BaseInternal { ) public payable { _handleTransferMessageValue(from, to, tokenId, msg.value); require( - _isApprovedOrOwner(msg.sender, tokenId), + _isApprovedOrOwner(_msgSender(), tokenId), 'ERC721: transfer caller is not owner or approved' ); _transfer(from, to, tokenId); @@ -89,7 +89,7 @@ abstract contract ERC721Base is IERC721Base, ERC721BaseInternal { ) public payable { _handleTransferMessageValue(from, to, tokenId, msg.value); require( - _isApprovedOrOwner(msg.sender, tokenId), + _isApprovedOrOwner(_msgSender(), tokenId), 'ERC721: transfer caller is not owner or approved' ); _safeTransfer(from, to, tokenId, data); @@ -103,7 +103,7 @@ abstract contract ERC721Base is IERC721Base, ERC721BaseInternal { address owner = ownerOf(tokenId); require(operator != owner, 'ERC721: approval to current owner'); require( - msg.sender == owner || isApprovedForAll(owner, msg.sender), + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), 'ERC721: approve caller is not owner nor approved for all' ); _approve(operator, tokenId); @@ -113,10 +113,10 @@ abstract contract ERC721Base is IERC721Base, ERC721BaseInternal { * @inheritdoc IERC721 */ function setApprovalForAll(address operator, bool status) public { - require(operator != msg.sender, 'ERC721: approve to caller'); - ERC721BaseStorage.layout().operatorApprovals[msg.sender][ + require(operator != _msgSender(), 'ERC721: approve to caller'); + ERC721BaseStorage.layout().operatorApprovals[_msgSender()][ operator ] = status; - emit ApprovalForAll(msg.sender, operator, status); + emit ApprovalForAll(_msgSender(), operator, status); } } diff --git a/contracts/token/ERC721/base/ERC721BaseInternal.sol b/contracts/token/ERC721/base/ERC721BaseInternal.sol index 8c1d7b624..810ed08d6 100644 --- a/contracts/token/ERC721/base/ERC721BaseInternal.sol +++ b/contracts/token/ERC721/base/ERC721BaseInternal.sol @@ -179,7 +179,7 @@ abstract contract ERC721BaseInternal is IERC721Internal { bytes memory returnData = to.functionCall( abi.encodeWithSelector( IERC721Receiver(to).onERC721Received.selector, - msg.sender, + _msgSender(), from, tokenId, data @@ -229,4 +229,24 @@ abstract contract ERC721BaseInternal is IERC721Internal { address to, uint256 tokenId ) internal virtual {} + + /* + * @notice Overrides the msgSender to enable delegation message signing. + * @returns address - The account whose authority is being acted on. + */ + function _msgSender() internal view virtual returns (address sender) { + if (msg.sender == address(this)) { + bytes memory array = msg.data; + uint256 index = msg.data.length; + assembly { + sender := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } else { + sender = msg.sender; + } + return sender; + } } From a0b566ebeb3b7d4998dd4349ae822e72c3318700 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 25 Aug 2022 18:44:44 -0700 Subject: [PATCH 02/33] Move msgSender trick to utils file --- contracts/access/ownable/OwnableInternal.sol | 23 ++------------- contracts/security/PausableInternal.sol | 23 ++------------- .../ERC1155/base/ERC1155BaseInternal.sol | 23 ++------------- .../token/ERC20/base/ERC20BaseInternal.sol | 23 ++------------- .../token/ERC721/base/ERC721BaseInternal.sol | 23 ++------------- contracts/utils/MsgSenderTrick.sol | 28 +++++++++++++++++++ 6 files changed, 38 insertions(+), 105 deletions(-) create mode 100644 contracts/utils/MsgSenderTrick.sol diff --git a/contracts/access/ownable/OwnableInternal.sol b/contracts/access/ownable/OwnableInternal.sol index 1b3bca8a6..b35966610 100644 --- a/contracts/access/ownable/OwnableInternal.sol +++ b/contracts/access/ownable/OwnableInternal.sol @@ -6,8 +6,9 @@ import { AddressUtils } from '../../utils/AddressUtils.sol'; import { IERC173 } from '../IERC173.sol'; import { IOwnableInternal } from './IOwnableInternal.sol'; import { OwnableStorage } from './OwnableStorage.sol'; +import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; -abstract contract OwnableInternal is IOwnableInternal { +abstract contract OwnableInternal is IOwnableInternal, MsgSenderTrick { using AddressUtils for address; using OwnableStorage for OwnableStorage.Layout; @@ -46,24 +47,4 @@ abstract contract OwnableInternal is IOwnableInternal { OwnableStorage.layout().setOwner(account); emit OwnershipTransferred(_msgSender(), account); } - - /* - * @notice Overrides the msgSender to enable delegation message signing. - * @returns address - The account whose authority is being acted on. - */ - function _msgSender() internal view virtual returns (address sender) { - if (msg.sender == address(this)) { - bytes memory array = msg.data; - uint256 index = msg.data.length; - assembly { - sender := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } - } else { - sender = msg.sender; - } - return sender; - } } diff --git a/contracts/security/PausableInternal.sol b/contracts/security/PausableInternal.sol index 50e9077b9..d696e43cb 100644 --- a/contracts/security/PausableInternal.sol +++ b/contracts/security/PausableInternal.sol @@ -3,11 +3,12 @@ pragma solidity ^0.8.8; import { PausableStorage } from './PausableStorage.sol'; +import { MsgSenderTrick } from '../utils/MsgSenderTrick.sol'; /** * @title Internal functions for Pausable security control module. */ -abstract contract PausableInternal { +abstract contract PausableInternal is MsgSenderTrick { using PausableStorage for PausableStorage.Layout; event Paused(address account); @@ -47,24 +48,4 @@ abstract contract PausableInternal { PausableStorage.layout().paused = false; emit Unpaused(_msgSender()); } - - /* - * @notice Overrides the msgSender to enable delegation message signing. - * @returns address - The account whose authority is being acted on. - */ - function _msgSender() internal view virtual returns (address sender) { - if (msg.sender == address(this)) { - bytes memory array = msg.data; - uint256 index = msg.data.length; - assembly { - sender := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } - } else { - sender = msg.sender; - } - return sender; - } } diff --git a/contracts/token/ERC1155/base/ERC1155BaseInternal.sol b/contracts/token/ERC1155/base/ERC1155BaseInternal.sol index 71e9bee02..4193809ca 100644 --- a/contracts/token/ERC1155/base/ERC1155BaseInternal.sol +++ b/contracts/token/ERC1155/base/ERC1155BaseInternal.sol @@ -6,12 +6,13 @@ import { AddressUtils } from '../../../utils/AddressUtils.sol'; import { IERC1155Internal } from '../IERC1155Internal.sol'; import { IERC1155Receiver } from '../IERC1155Receiver.sol'; import { ERC1155BaseStorage } from './ERC1155BaseStorage.sol'; +import { MsgSenderTrick } from '../../../utils/MsgSenderTrick.sol'; /** * @title Base ERC1155 internal functions * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license) */ -abstract contract ERC1155BaseInternal is IERC1155Internal { +abstract contract ERC1155BaseInternal is IERC1155Internal, MsgSenderTrick { using AddressUtils for address; /** @@ -511,24 +512,4 @@ abstract contract ERC1155BaseInternal is IERC1155Internal { uint256[] memory amounts, bytes memory data ) internal virtual {} - - /* - * @notice Overrides the msgSender to enable delegation message signing. - * @returns address - The account whose authority is being acted on. - */ - function _msgSender() internal view virtual returns (address sender) { - if (msg.sender == address(this)) { - bytes memory array = msg.data; - uint256 index = msg.data.length; - assembly { - sender := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } - } else { - sender = msg.sender; - } - return sender; - } } diff --git a/contracts/token/ERC20/base/ERC20BaseInternal.sol b/contracts/token/ERC20/base/ERC20BaseInternal.sol index 0f38c5ce3..c64527bd8 100644 --- a/contracts/token/ERC20/base/ERC20BaseInternal.sol +++ b/contracts/token/ERC20/base/ERC20BaseInternal.sol @@ -4,11 +4,12 @@ pragma solidity ^0.8.8; import { IERC20BaseInternal } from './IERC20BaseInternal.sol'; import { ERC20BaseStorage } from './ERC20BaseStorage.sol'; +import { MsgSenderTrick } from '../../../utils/MsgSenderTrick.sol'; /** * @title Base ERC20 internal functions, excluding optional extensions */ -abstract contract ERC20BaseInternal is IERC20BaseInternal { +abstract contract ERC20BaseInternal is IERC20BaseInternal, MsgSenderTrick { /** * @notice query the total minted token supply * @return token supply @@ -179,24 +180,4 @@ abstract contract ERC20BaseInternal is IERC20BaseInternal { address to, uint256 amount ) internal virtual {} - - /* - * @notice Overrides the msgSender to enable delegation message signing. - * @returns address - The account whose authority is being acted on. - */ - function _msgSender() internal view virtual returns (address sender) { - if (msg.sender == address(this)) { - bytes memory array = msg.data; - uint256 index = msg.data.length; - assembly { - sender := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } - } else { - sender = msg.sender; - } - return sender; - } } diff --git a/contracts/token/ERC721/base/ERC721BaseInternal.sol b/contracts/token/ERC721/base/ERC721BaseInternal.sol index 810ed08d6..868fc4f17 100644 --- a/contracts/token/ERC721/base/ERC721BaseInternal.sol +++ b/contracts/token/ERC721/base/ERC721BaseInternal.sol @@ -8,11 +8,12 @@ import { EnumerableSet } from '../../../utils/EnumerableSet.sol'; import { IERC721Internal } from '../IERC721Internal.sol'; import { IERC721Receiver } from '../IERC721Receiver.sol'; import { ERC721BaseStorage } from './ERC721BaseStorage.sol'; +import { MsgSenderTrick } from '../../../utils/MsgSenderTrick.sol'; /** * @title Base ERC721 internal functions */ -abstract contract ERC721BaseInternal is IERC721Internal { +abstract contract ERC721BaseInternal is IERC721Internal, MsgSenderTrick { using ERC721BaseStorage for ERC721BaseStorage.Layout; using AddressUtils for address; using EnumerableMap for EnumerableMap.UintToAddressMap; @@ -229,24 +230,4 @@ abstract contract ERC721BaseInternal is IERC721Internal { address to, uint256 tokenId ) internal virtual {} - - /* - * @notice Overrides the msgSender to enable delegation message signing. - * @returns address - The account whose authority is being acted on. - */ - function _msgSender() internal view virtual returns (address sender) { - if (msg.sender == address(this)) { - bytes memory array = msg.data; - uint256 index = msg.data.length; - assembly { - sender := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } - } else { - sender = msg.sender; - } - return sender; - } } diff --git a/contracts/utils/MsgSenderTrick.sol b/contracts/utils/MsgSenderTrick.sol new file mode 100644 index 000000000..e9911248e --- /dev/null +++ b/contracts/utils/MsgSenderTrick.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.8; + +/** + * @title Utility contract for supporting alternative authorization schemes + */ +abstract contract MsgSenderTrick { + /* + * @notice Overrides the msgSender to enable delegation message signing. + * @returns address - The account whose authority is being acted on. + */ + function _msgSender() internal view virtual returns (address sender) { + if (msg.sender == address(this)) { + bytes memory array = msg.data; + uint256 index = msg.data.length; + assembly { + sender := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } else { + sender = msg.sender; + } + return sender; + } +} From 3e876b3c35cf9d99761468bbeee19647966d1c3d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 25 Aug 2022 19:25:00 -0700 Subject: [PATCH 03/33] Bring in the better documented version of _msgSender --- contracts/utils/MsgSenderTrick.sol | 44 ++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/contracts/utils/MsgSenderTrick.sol b/contracts/utils/MsgSenderTrick.sol index e9911248e..bf84eabe6 100644 --- a/contracts/utils/MsgSenderTrick.sol +++ b/contracts/utils/MsgSenderTrick.sol @@ -7,22 +7,48 @@ pragma solidity ^0.8.8; */ abstract contract MsgSenderTrick { /* - * @notice Overrides the msgSender to enable delegation message signing. + * @notice Returns the intended sender of a message. Either msg.sender or the address of the authorizing signer. + * Enables MetaTransactions, since the sender doesn't need to be the tx.origin or even the msg.sender. * @returns address - The account whose authority is being acted on. + * and the end-user for GSN relayed calls (where msg.sender is actually `RelayHub`). + * + * IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.sender`, and use {_msgSender} instead. */ function _msgSender() internal view virtual returns (address sender) { if (msg.sender == address(this)) { - bytes memory array = msg.data; - uint256 index = msg.data.length; - assembly { - sender := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } + return _getRelayedCallSender(); } else { sender = msg.sender; } return sender; } + + function _getRelayedCallSender() + private + pure + returns (address payable result) + { + // We need to read 20 bytes (an address) located at array index msg.data.length - 20. In memory, the array + // is prefixed with a 32-byte length value, so we first add 32 to get the memory read index. However, doing + // so would leave the address in the upper 20 bytes of the 32-byte word, which is inconvenient and would + // require bit shifting. We therefore subtract 12 from the read index so the address lands on the lower 20 + // bytes. This can always be done due to the 32-byte prefix. + + // The final memory read index is msg.data.length - 20 + 32 - 12 = msg.data.length. Using inline assembly is the + // easiest/most-efficient way to perform this operation. + + // These fields are not accessible from assembly + bytes memory array = msg.data; + uint256 index = msg.data.length; + + // solhint-disable-next-line no-inline-assembly + assembly { + // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. + result := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + return result; + } } From bbb0b616482c79a2145f490ee4895a2afb3ad048 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Mon, 17 Mar 2025 00:54:06 -0600 Subject: [PATCH 04/33] fix pragma statement --- contracts/utils/MsgSenderTrick.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/MsgSenderTrick.sol b/contracts/utils/MsgSenderTrick.sol index bf84eabe6..19fec0aa2 100644 --- a/contracts/utils/MsgSenderTrick.sol +++ b/contracts/utils/MsgSenderTrick.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.8; +pragma solidity ^0.8.20; /** * @title Utility contract for supporting alternative authorization schemes From 63366d272affbf57509168a563102346ecae10b6 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Mon, 17 Mar 2025 01:08:25 -0600 Subject: [PATCH 05/33] update MsgSenderTrick to comply with layers pattern --- contracts/access/access_control/_AccessControl.sol | 4 ++-- contracts/access/access_control/_IAccessControl.sol | 4 +++- contracts/access/ownable/_IOwnable.sol | 3 ++- contracts/access/ownable/_Ownable.sol | 4 ++-- .../security/partially_pausable/_IPartiallyPausable.sol | 4 +++- .../security/partially_pausable/_PartiallyPausable.sol | 4 ++-- contracts/security/pausable/_IPausable.sol | 4 +++- contracts/security/pausable/_Pausable.sol | 4 ++-- contracts/token/fungible/_FungibleToken.sol | 4 ++-- contracts/token/fungible/_IFungibleToken.sol | 3 ++- contracts/token/multi/_IMultiToken.sol | 3 ++- contracts/token/multi/_MultiToken.sol | 8 ++++++-- contracts/token/non_fungible/_INonFungibleToken.sol | 3 ++- contracts/token/non_fungible/_NonFungibleToken.sol | 4 ++-- contracts/utils/_IMsgSenderTrick.sol | 5 +++++ .../utils/{MsgSenderTrick.sol => _MsgSenderTrick.sol} | 3 ++- 16 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 contracts/utils/_IMsgSenderTrick.sol rename contracts/utils/{MsgSenderTrick.sol => _MsgSenderTrick.sol} (95%) diff --git a/contracts/access/access_control/_AccessControl.sol b/contracts/access/access_control/_AccessControl.sol index 9b018d2b7..98b3d2822 100644 --- a/contracts/access/access_control/_AccessControl.sol +++ b/contracts/access/access_control/_AccessControl.sol @@ -6,14 +6,14 @@ import { EnumerableSet } from '../../data/EnumerableSet.sol'; import { AccessControlStorage } from '../../storage/AccessControlStorage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; import { UintUtils } from '../../utils/UintUtils.sol'; -import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; +import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; import { _IAccessControl } from './_IAccessControl.sol'; /** * @title Role-based access control system * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license) */ -abstract contract _AccessControl is _IAccessControl, MsgSenderTrick { +abstract contract _AccessControl is _IAccessControl, _MsgSenderTrick { using AddressUtils for address; using EnumerableSet for EnumerableSet.AddressSet; using UintUtils for uint256; diff --git a/contracts/access/access_control/_IAccessControl.sol b/contracts/access/access_control/_IAccessControl.sol index 4dd871cb7..53216fdf6 100644 --- a/contracts/access/access_control/_IAccessControl.sol +++ b/contracts/access/access_control/_IAccessControl.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.20; +import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; + /** * @title Partial AccessControl interface needed by internal functions */ -interface _IAccessControl { +interface _IAccessControl is _IMsgSenderTrick { event RoleAdminChanged( bytes32 indexed role, bytes32 indexed previousAdminRole, diff --git a/contracts/access/ownable/_IOwnable.sol b/contracts/access/ownable/_IOwnable.sol index 1fda6acb8..ddf13984e 100644 --- a/contracts/access/ownable/_IOwnable.sol +++ b/contracts/access/ownable/_IOwnable.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.20; import { _IERC173 } from '../../interfaces/_IERC173.sol'; +import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; -interface _IOwnable is _IERC173 { +interface _IOwnable is _IERC173, _IMsgSenderTrick { error Ownable__NotOwner(); error Ownable__NotTransitiveOwner(); } diff --git a/contracts/access/ownable/_Ownable.sol b/contracts/access/ownable/_Ownable.sol index e433aba06..178fb772f 100644 --- a/contracts/access/ownable/_Ownable.sol +++ b/contracts/access/ownable/_Ownable.sol @@ -5,10 +5,10 @@ pragma solidity ^0.8.20; import { IERC173 } from '../../interfaces/IERC173.sol'; import { ERC173Storage } from '../../storage/ERC173Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; +import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; import { _IOwnable } from './_IOwnable.sol'; -abstract contract _Ownable is _IOwnable, MsgSenderTrick { +abstract contract _Ownable is _IOwnable, _MsgSenderTrick { using AddressUtils for address; modifier onlyOwner() { diff --git a/contracts/security/partially_pausable/_IPartiallyPausable.sol b/contracts/security/partially_pausable/_IPartiallyPausable.sol index 637c069dd..c6eedb2f9 100644 --- a/contracts/security/partially_pausable/_IPartiallyPausable.sol +++ b/contracts/security/partially_pausable/_IPartiallyPausable.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.20; -interface _IPartiallyPausable { +import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; + +interface _IPartiallyPausable is _IMsgSenderTrick { error PartiallyPausable__PartiallyPaused(); error PartiallyPausable__NotPartiallyPaused(); diff --git a/contracts/security/partially_pausable/_PartiallyPausable.sol b/contracts/security/partially_pausable/_PartiallyPausable.sol index 025c8dbf5..d610f497b 100644 --- a/contracts/security/partially_pausable/_PartiallyPausable.sol +++ b/contracts/security/partially_pausable/_PartiallyPausable.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.20; import { PausableStorage } from '../../storage/PausableStorage.sol'; -import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; +import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; import { _IPartiallyPausable } from './_IPartiallyPausable.sol'; /** * @title Internal functions for PartiallyPausable security control module. */ -abstract contract _PartiallyPausable is _IPartiallyPausable, MsgSenderTrick { +abstract contract _PartiallyPausable is _IPartiallyPausable, _MsgSenderTrick { modifier whenNotPartiallyPaused(bytes32 key) { if (_partiallyPaused(key)) revert PartiallyPausable__PartiallyPaused(); _; diff --git a/contracts/security/pausable/_IPausable.sol b/contracts/security/pausable/_IPausable.sol index 5822ef59d..eea73037c 100644 --- a/contracts/security/pausable/_IPausable.sol +++ b/contracts/security/pausable/_IPausable.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.20; -interface _IPausable { +import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; + +interface _IPausable is _IMsgSenderTrick { error Pausable__Paused(); error Pausable__NotPaused(); diff --git a/contracts/security/pausable/_Pausable.sol b/contracts/security/pausable/_Pausable.sol index 07cf5972d..50e4ad0a3 100644 --- a/contracts/security/pausable/_Pausable.sol +++ b/contracts/security/pausable/_Pausable.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.20; import { PausableStorage } from '../../storage/PausableStorage.sol'; -import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; +import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; import { _IPausable } from './_IPausable.sol'; /** * @title Internal functions for Pausable security control module. */ -abstract contract _Pausable is _IPausable, MsgSenderTrick { +abstract contract _Pausable is _IPausable, _MsgSenderTrick { modifier whenNotPaused() { if (_paused()) revert Pausable__Paused(); _; diff --git a/contracts/token/fungible/_FungibleToken.sol b/contracts/token/fungible/_FungibleToken.sol index ebbf43553..9cfab028f 100644 --- a/contracts/token/fungible/_FungibleToken.sol +++ b/contracts/token/fungible/_FungibleToken.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.20; import { ERC20Storage } from '../../storage/ERC20Storage.sol'; -import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; +import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; import { _IFungibleToken } from './_IFungibleToken.sol'; /** * @title Base FungibleToken internal functions, excluding optional extensions */ -abstract contract _FungibleToken is _IFungibleToken, MsgSenderTrick { +abstract contract _FungibleToken is _IFungibleToken, _MsgSenderTrick { /** * @notice query the total minted token supply * @return token supply diff --git a/contracts/token/fungible/_IFungibleToken.sol b/contracts/token/fungible/_IFungibleToken.sol index 33397ad3a..5e545bb7a 100644 --- a/contracts/token/fungible/_IFungibleToken.sol +++ b/contracts/token/fungible/_IFungibleToken.sol @@ -3,11 +3,12 @@ pragma solidity ^0.8.20; import { _IERC20 } from '../../interfaces/_IERC20.sol'; +import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; /** * @title FungibleToken base interface */ -interface _IFungibleToken is _IERC20 { +interface _IFungibleToken is _IERC20, _IMsgSenderTrick { error FungibleToken__ApproveFromZeroAddress(); error FungibleToken__ApproveToZeroAddress(); error FungibleToken__BurnExceedsBalance(); diff --git a/contracts/token/multi/_IMultiToken.sol b/contracts/token/multi/_IMultiToken.sol index 61ad7dc2d..e087df520 100644 --- a/contracts/token/multi/_IMultiToken.sol +++ b/contracts/token/multi/_IMultiToken.sol @@ -4,11 +4,12 @@ pragma solidity ^0.8.20; import { _IERC1155 } from '../../interfaces/_IERC1155.sol'; import { _IIntrospectable } from '../../introspection/_IIntrospectable.sol'; +import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; /** * @title MultiToken base interface */ -interface _IMultiToken is _IERC1155, _IIntrospectable { +interface _IMultiToken is _IERC1155, _IIntrospectable, _IMsgSenderTrick { error MultiToken__ArrayLengthMismatch(); error MultiToken__BalanceQueryZeroAddress(); error MultiToken__NotOwnerOrApproved(); diff --git a/contracts/token/multi/_MultiToken.sol b/contracts/token/multi/_MultiToken.sol index e2f9c927c..388de360a 100644 --- a/contracts/token/multi/_MultiToken.sol +++ b/contracts/token/multi/_MultiToken.sol @@ -6,14 +6,18 @@ import { IERC1155Receiver } from '../../interfaces/IERC1155Receiver.sol'; import { _Introspectable } from '../../introspection/_Introspectable.sol'; import { ERC1155Storage } from '../../storage/ERC1155Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; +import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; import { _IMultiToken } from './_IMultiToken.sol'; /** * @title Base MultiToken internal functions * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license) */ -abstract contract _MultiToken is _IMultiToken, _Introspectable, MsgSenderTrick { +abstract contract _MultiToken is + _IMultiToken, + _Introspectable, + _MsgSenderTrick +{ using AddressUtils for address; /** diff --git a/contracts/token/non_fungible/_INonFungibleToken.sol b/contracts/token/non_fungible/_INonFungibleToken.sol index d07d783f7..dc1b42ec7 100644 --- a/contracts/token/non_fungible/_INonFungibleToken.sol +++ b/contracts/token/non_fungible/_INonFungibleToken.sol @@ -4,11 +4,12 @@ pragma solidity ^0.8.20; import { _IERC721 } from '../../interfaces/_IERC721.sol'; import { _IIntrospectable } from '../../introspection/_IIntrospectable.sol'; +import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; /** * @title NonFungibleToken base interface */ -interface _INonFungibleToken is _IERC721, _IIntrospectable { +interface _INonFungibleToken is _IERC721, _IIntrospectable, _IMsgSenderTrick { error NonFungibleToken__NotOwnerOrApproved(); error NonFungibleToken__SelfApproval(); error NonFungibleToken__BalanceQueryZeroAddress(); diff --git a/contracts/token/non_fungible/_NonFungibleToken.sol b/contracts/token/non_fungible/_NonFungibleToken.sol index 1bb461eea..f924494bf 100644 --- a/contracts/token/non_fungible/_NonFungibleToken.sol +++ b/contracts/token/non_fungible/_NonFungibleToken.sol @@ -8,7 +8,7 @@ import { IERC721Receiver } from '../../interfaces/IERC721Receiver.sol'; import { _Introspectable } from '../../introspection/_Introspectable.sol'; import { ERC721Storage } from '../../storage/ERC721Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { MsgSenderTrick } from '../../utils/MsgSenderTrick.sol'; +import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; import { _INonFungibleToken } from './_INonFungibleToken.sol'; /** @@ -17,7 +17,7 @@ import { _INonFungibleToken } from './_INonFungibleToken.sol'; abstract contract _NonFungibleToken is _INonFungibleToken, _Introspectable, - MsgSenderTrick + _MsgSenderTrick { using AddressUtils for address; using EnumerableMap for EnumerableMap.UintToAddressMap; diff --git a/contracts/utils/_IMsgSenderTrick.sol b/contracts/utils/_IMsgSenderTrick.sol new file mode 100644 index 000000000..f4f26e170 --- /dev/null +++ b/contracts/utils/_IMsgSenderTrick.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +interface _IMsgSenderTrick {} diff --git a/contracts/utils/MsgSenderTrick.sol b/contracts/utils/_MsgSenderTrick.sol similarity index 95% rename from contracts/utils/MsgSenderTrick.sol rename to contracts/utils/_MsgSenderTrick.sol index 19fec0aa2..e4d362d9d 100644 --- a/contracts/utils/MsgSenderTrick.sol +++ b/contracts/utils/_MsgSenderTrick.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.20; +import { _IMsgSenderTrick } from './_IMsgSenderTrick.sol'; /** * @title Utility contract for supporting alternative authorization schemes */ -abstract contract MsgSenderTrick { +abstract contract _MsgSenderTrick is _IMsgSenderTrick { /* * @notice Returns the intended sender of a message. Either msg.sender or the address of the authorizing signer. * Enables MetaTransactions, since the sender doesn't need to be the tx.origin or even the msg.sender. From 9a0af0ad47fd5d4a4a63641a6e435da42d11aee7 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:09:51 -0600 Subject: [PATCH 06/33] rename MsgSenderTrick to Context --- contracts/access/access_control/_AccessControl.sol | 4 ++-- contracts/access/access_control/_IAccessControl.sol | 4 ++-- contracts/access/ownable/_IOwnable.sol | 4 ++-- contracts/access/ownable/_Ownable.sol | 4 ++-- .../security/partially_pausable/_IPartiallyPausable.sol | 4 ++-- .../security/partially_pausable/_PartiallyPausable.sol | 4 ++-- contracts/security/pausable/_IPausable.sol | 4 ++-- contracts/security/pausable/_Pausable.sol | 4 ++-- contracts/token/fungible/_FungibleToken.sol | 4 ++-- contracts/token/fungible/_IFungibleToken.sol | 4 ++-- contracts/token/multi/_IMultiToken.sol | 4 ++-- contracts/token/multi/_MultiToken.sol | 8 ++------ contracts/token/non_fungible/_INonFungibleToken.sol | 4 ++-- contracts/token/non_fungible/_NonFungibleToken.sol | 4 ++-- contracts/utils/{_MsgSenderTrick.sol => _Context.sol} | 4 ++-- contracts/utils/{_IMsgSenderTrick.sol => _IContext.sol} | 2 +- 16 files changed, 31 insertions(+), 35 deletions(-) rename contracts/utils/{_MsgSenderTrick.sol => _Context.sol} (95%) rename contracts/utils/{_IMsgSenderTrick.sol => _IContext.sol} (66%) diff --git a/contracts/access/access_control/_AccessControl.sol b/contracts/access/access_control/_AccessControl.sol index 98b3d2822..adad27111 100644 --- a/contracts/access/access_control/_AccessControl.sol +++ b/contracts/access/access_control/_AccessControl.sol @@ -6,14 +6,14 @@ import { EnumerableSet } from '../../data/EnumerableSet.sol'; import { AccessControlStorage } from '../../storage/AccessControlStorage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; import { UintUtils } from '../../utils/UintUtils.sol'; -import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; +import { _Context } from '../../utils/_Context.sol'; import { _IAccessControl } from './_IAccessControl.sol'; /** * @title Role-based access control system * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license) */ -abstract contract _AccessControl is _IAccessControl, _MsgSenderTrick { +abstract contract _AccessControl is _IAccessControl, _Context { using AddressUtils for address; using EnumerableSet for EnumerableSet.AddressSet; using UintUtils for uint256; diff --git a/contracts/access/access_control/_IAccessControl.sol b/contracts/access/access_control/_IAccessControl.sol index 53216fdf6..0c089851c 100644 --- a/contracts/access/access_control/_IAccessControl.sol +++ b/contracts/access/access_control/_IAccessControl.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.20; -import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; +import { _IContext } from '../../utils/_IContext.sol'; /** * @title Partial AccessControl interface needed by internal functions */ -interface _IAccessControl is _IMsgSenderTrick { +interface _IAccessControl is _IContext { event RoleAdminChanged( bytes32 indexed role, bytes32 indexed previousAdminRole, diff --git a/contracts/access/ownable/_IOwnable.sol b/contracts/access/ownable/_IOwnable.sol index ddf13984e..b3ec83597 100644 --- a/contracts/access/ownable/_IOwnable.sol +++ b/contracts/access/ownable/_IOwnable.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.20; import { _IERC173 } from '../../interfaces/_IERC173.sol'; -import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; +import { _IContext } from '../../utils/_IContext.sol'; -interface _IOwnable is _IERC173, _IMsgSenderTrick { +interface _IOwnable is _IERC173, _IContext { error Ownable__NotOwner(); error Ownable__NotTransitiveOwner(); } diff --git a/contracts/access/ownable/_Ownable.sol b/contracts/access/ownable/_Ownable.sol index 178fb772f..d53c21a90 100644 --- a/contracts/access/ownable/_Ownable.sol +++ b/contracts/access/ownable/_Ownable.sol @@ -5,10 +5,10 @@ pragma solidity ^0.8.20; import { IERC173 } from '../../interfaces/IERC173.sol'; import { ERC173Storage } from '../../storage/ERC173Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; +import { _Context } from '../../utils/_Context.sol'; import { _IOwnable } from './_IOwnable.sol'; -abstract contract _Ownable is _IOwnable, _MsgSenderTrick { +abstract contract _Ownable is _IOwnable, _Context { using AddressUtils for address; modifier onlyOwner() { diff --git a/contracts/security/partially_pausable/_IPartiallyPausable.sol b/contracts/security/partially_pausable/_IPartiallyPausable.sol index c6eedb2f9..1705072c5 100644 --- a/contracts/security/partially_pausable/_IPartiallyPausable.sol +++ b/contracts/security/partially_pausable/_IPartiallyPausable.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.20; -import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; +import { _IContext } from '../../utils/_IContext.sol'; -interface _IPartiallyPausable is _IMsgSenderTrick { +interface _IPartiallyPausable is _IContext { error PartiallyPausable__PartiallyPaused(); error PartiallyPausable__NotPartiallyPaused(); diff --git a/contracts/security/partially_pausable/_PartiallyPausable.sol b/contracts/security/partially_pausable/_PartiallyPausable.sol index d610f497b..f510d1334 100644 --- a/contracts/security/partially_pausable/_PartiallyPausable.sol +++ b/contracts/security/partially_pausable/_PartiallyPausable.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.20; import { PausableStorage } from '../../storage/PausableStorage.sol'; -import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; +import { _Context } from '../../utils/_Context.sol'; import { _IPartiallyPausable } from './_IPartiallyPausable.sol'; /** * @title Internal functions for PartiallyPausable security control module. */ -abstract contract _PartiallyPausable is _IPartiallyPausable, _MsgSenderTrick { +abstract contract _PartiallyPausable is _IPartiallyPausable, _Context { modifier whenNotPartiallyPaused(bytes32 key) { if (_partiallyPaused(key)) revert PartiallyPausable__PartiallyPaused(); _; diff --git a/contracts/security/pausable/_IPausable.sol b/contracts/security/pausable/_IPausable.sol index eea73037c..1c2dca946 100644 --- a/contracts/security/pausable/_IPausable.sol +++ b/contracts/security/pausable/_IPausable.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.20; -import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; +import { _IContext } from '../../utils/_IContext.sol'; -interface _IPausable is _IMsgSenderTrick { +interface _IPausable is _IContext { error Pausable__Paused(); error Pausable__NotPaused(); diff --git a/contracts/security/pausable/_Pausable.sol b/contracts/security/pausable/_Pausable.sol index 50e4ad0a3..2e6bf39da 100644 --- a/contracts/security/pausable/_Pausable.sol +++ b/contracts/security/pausable/_Pausable.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.20; import { PausableStorage } from '../../storage/PausableStorage.sol'; -import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; +import { _Context } from '../../utils/_Context.sol'; import { _IPausable } from './_IPausable.sol'; /** * @title Internal functions for Pausable security control module. */ -abstract contract _Pausable is _IPausable, _MsgSenderTrick { +abstract contract _Pausable is _IPausable, _Context { modifier whenNotPaused() { if (_paused()) revert Pausable__Paused(); _; diff --git a/contracts/token/fungible/_FungibleToken.sol b/contracts/token/fungible/_FungibleToken.sol index 9cfab028f..440b5ae81 100644 --- a/contracts/token/fungible/_FungibleToken.sol +++ b/contracts/token/fungible/_FungibleToken.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.20; import { ERC20Storage } from '../../storage/ERC20Storage.sol'; -import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; +import { _Context } from '../../utils/_Context.sol'; import { _IFungibleToken } from './_IFungibleToken.sol'; /** * @title Base FungibleToken internal functions, excluding optional extensions */ -abstract contract _FungibleToken is _IFungibleToken, _MsgSenderTrick { +abstract contract _FungibleToken is _IFungibleToken, _Context { /** * @notice query the total minted token supply * @return token supply diff --git a/contracts/token/fungible/_IFungibleToken.sol b/contracts/token/fungible/_IFungibleToken.sol index 5e545bb7a..0757ff671 100644 --- a/contracts/token/fungible/_IFungibleToken.sol +++ b/contracts/token/fungible/_IFungibleToken.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.20; import { _IERC20 } from '../../interfaces/_IERC20.sol'; -import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; +import { _IContext } from '../../utils/_IContext.sol'; /** * @title FungibleToken base interface */ -interface _IFungibleToken is _IERC20, _IMsgSenderTrick { +interface _IFungibleToken is _IERC20, _IContext { error FungibleToken__ApproveFromZeroAddress(); error FungibleToken__ApproveToZeroAddress(); error FungibleToken__BurnExceedsBalance(); diff --git a/contracts/token/multi/_IMultiToken.sol b/contracts/token/multi/_IMultiToken.sol index e087df520..214bc256e 100644 --- a/contracts/token/multi/_IMultiToken.sol +++ b/contracts/token/multi/_IMultiToken.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.20; import { _IERC1155 } from '../../interfaces/_IERC1155.sol'; import { _IIntrospectable } from '../../introspection/_IIntrospectable.sol'; -import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; +import { _IContext } from '../../utils/_IContext.sol'; /** * @title MultiToken base interface */ -interface _IMultiToken is _IERC1155, _IIntrospectable, _IMsgSenderTrick { +interface _IMultiToken is _IERC1155, _IIntrospectable, _IContext { error MultiToken__ArrayLengthMismatch(); error MultiToken__BalanceQueryZeroAddress(); error MultiToken__NotOwnerOrApproved(); diff --git a/contracts/token/multi/_MultiToken.sol b/contracts/token/multi/_MultiToken.sol index 388de360a..cbffd0b3f 100644 --- a/contracts/token/multi/_MultiToken.sol +++ b/contracts/token/multi/_MultiToken.sol @@ -6,18 +6,14 @@ import { IERC1155Receiver } from '../../interfaces/IERC1155Receiver.sol'; import { _Introspectable } from '../../introspection/_Introspectable.sol'; import { ERC1155Storage } from '../../storage/ERC1155Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; +import { _Context } from '../../utils/_Context.sol'; import { _IMultiToken } from './_IMultiToken.sol'; /** * @title Base MultiToken internal functions * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license) */ -abstract contract _MultiToken is - _IMultiToken, - _Introspectable, - _MsgSenderTrick -{ +abstract contract _MultiToken is _IMultiToken, _Introspectable, _Context { using AddressUtils for address; /** diff --git a/contracts/token/non_fungible/_INonFungibleToken.sol b/contracts/token/non_fungible/_INonFungibleToken.sol index dc1b42ec7..bc4de714a 100644 --- a/contracts/token/non_fungible/_INonFungibleToken.sol +++ b/contracts/token/non_fungible/_INonFungibleToken.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.20; import { _IERC721 } from '../../interfaces/_IERC721.sol'; import { _IIntrospectable } from '../../introspection/_IIntrospectable.sol'; -import { _IMsgSenderTrick } from '../../utils/_IMsgSenderTrick.sol'; +import { _IContext } from '../../utils/_IContext.sol'; /** * @title NonFungibleToken base interface */ -interface _INonFungibleToken is _IERC721, _IIntrospectable, _IMsgSenderTrick { +interface _INonFungibleToken is _IERC721, _IIntrospectable, _IContext { error NonFungibleToken__NotOwnerOrApproved(); error NonFungibleToken__SelfApproval(); error NonFungibleToken__BalanceQueryZeroAddress(); diff --git a/contracts/token/non_fungible/_NonFungibleToken.sol b/contracts/token/non_fungible/_NonFungibleToken.sol index 49d4c8429..899d84240 100644 --- a/contracts/token/non_fungible/_NonFungibleToken.sol +++ b/contracts/token/non_fungible/_NonFungibleToken.sol @@ -8,7 +8,7 @@ import { IERC721Receiver } from '../../interfaces/IERC721Receiver.sol'; import { _Introspectable } from '../../introspection/_Introspectable.sol'; import { ERC721Storage } from '../../storage/ERC721Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { _MsgSenderTrick } from '../../utils/_MsgSenderTrick.sol'; +import { _Context } from '../../utils/_Context.sol'; import { _INonFungibleToken } from './_INonFungibleToken.sol'; /** @@ -17,7 +17,7 @@ import { _INonFungibleToken } from './_INonFungibleToken.sol'; abstract contract _NonFungibleToken is _INonFungibleToken, _Introspectable, - _MsgSenderTrick + _Context { using AddressUtils for address; using EnumerableMap for EnumerableMap.UintToAddressMap; diff --git a/contracts/utils/_MsgSenderTrick.sol b/contracts/utils/_Context.sol similarity index 95% rename from contracts/utils/_MsgSenderTrick.sol rename to contracts/utils/_Context.sol index e4d362d9d..2a0107fc2 100644 --- a/contracts/utils/_MsgSenderTrick.sol +++ b/contracts/utils/_Context.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.20; -import { _IMsgSenderTrick } from './_IMsgSenderTrick.sol'; +import { _IContext } from './_IContext.sol'; /** * @title Utility contract for supporting alternative authorization schemes */ -abstract contract _MsgSenderTrick is _IMsgSenderTrick { +abstract contract _Context is _IContext { /* * @notice Returns the intended sender of a message. Either msg.sender or the address of the authorizing signer. * Enables MetaTransactions, since the sender doesn't need to be the tx.origin or even the msg.sender. diff --git a/contracts/utils/_IMsgSenderTrick.sol b/contracts/utils/_IContext.sol similarity index 66% rename from contracts/utils/_IMsgSenderTrick.sol rename to contracts/utils/_IContext.sol index f4f26e170..c1a257897 100644 --- a/contracts/utils/_IMsgSenderTrick.sol +++ b/contracts/utils/_IContext.sol @@ -2,4 +2,4 @@ pragma solidity ^0.8.20; -interface _IMsgSenderTrick {} +interface _IContext {} From 6c93a45050cd3c73c3d65e84e71bc494dc8fc498 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:29:42 -0600 Subject: [PATCH 07/33] expand _Context to _MetaTransactionContext --- contracts/utils/_Context.sol | 46 +--------------- contracts/utils/_IMetaTransactionContext.sol | 7 +++ contracts/utils/_MetaTransactionContext.sol | 58 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 44 deletions(-) create mode 100644 contracts/utils/_IMetaTransactionContext.sol create mode 100644 contracts/utils/_MetaTransactionContext.sol diff --git a/contracts/utils/_Context.sol b/contracts/utils/_Context.sol index 2a0107fc2..dd8272844 100644 --- a/contracts/utils/_Context.sol +++ b/contracts/utils/_Context.sol @@ -7,49 +7,7 @@ import { _IContext } from './_IContext.sol'; * @title Utility contract for supporting alternative authorization schemes */ abstract contract _Context is _IContext { - /* - * @notice Returns the intended sender of a message. Either msg.sender or the address of the authorizing signer. - * Enables MetaTransactions, since the sender doesn't need to be the tx.origin or even the msg.sender. - * @returns address - The account whose authority is being acted on. - * and the end-user for GSN relayed calls (where msg.sender is actually `RelayHub`). - * - * IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.sender`, and use {_msgSender} instead. - */ - function _msgSender() internal view virtual returns (address sender) { - if (msg.sender == address(this)) { - return _getRelayedCallSender(); - } else { - sender = msg.sender; - } - return sender; - } - - function _getRelayedCallSender() - private - pure - returns (address payable result) - { - // We need to read 20 bytes (an address) located at array index msg.data.length - 20. In memory, the array - // is prefixed with a 32-byte length value, so we first add 32 to get the memory read index. However, doing - // so would leave the address in the upper 20 bytes of the 32-byte word, which is inconvenient and would - // require bit shifting. We therefore subtract 12 from the read index so the address lands on the lower 20 - // bytes. This can always be done due to the 32-byte prefix. - - // The final memory read index is msg.data.length - 20 + 32 - 12 = msg.data.length. Using inline assembly is the - // easiest/most-efficient way to perform this operation. - - // These fields are not accessible from assembly - bytes memory array = msg.data; - uint256 index = msg.data.length; - - // solhint-disable-next-line no-inline-assembly - assembly { - // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. - result := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } - return result; + function _msgSender() internal view virtual returns (address msgSender) { + msgSender = msg.sender; } } diff --git a/contracts/utils/_IMetaTransactionContext.sol b/contracts/utils/_IMetaTransactionContext.sol new file mode 100644 index 000000000..3c4a8af2a --- /dev/null +++ b/contracts/utils/_IMetaTransactionContext.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { _IContext } from './_IContext.sol'; + +interface _IMetaTransactionContext is _IContext {} diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_MetaTransactionContext.sol new file mode 100644 index 000000000..7fd07c295 --- /dev/null +++ b/contracts/utils/_MetaTransactionContext.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { _Context } from './_Context.sol'; + +abstract contract _MetaTransactionContext is _Context { + /* + * @notice Returns the intended sender of a message. Either msg.sender or the address of the authorizing signer. + * Enables MetaTransactions, since the sender doesn't need to be the tx.origin or even the msg.sender. + * @returns address - The account whose authority is being acted on. + * and the end-user for GSN relayed calls (where msg.sender is actually `RelayHub`). + * + * IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.sender`, and use {_msgSender} instead. + */ + function _msgSender() + internal + view + virtual + override + returns (address msgSender) + { + if (msg.sender == address(this)) { + msgSender = _getRelayedCallSender(); + } else { + msgSender = msg.sender; + } + } + + function _getRelayedCallSender() + private + pure + returns (address payable result) + { + // We need to read 20 bytes (an address) located at array index msg.data.length - 20. In memory, the array + // is prefixed with a 32-byte length value, so we first add 32 to get the memory read index. However, doing + // so would leave the address in the upper 20 bytes of the 32-byte word, which is inconvenient and would + // require bit shifting. We therefore subtract 12 from the read index so the address lands on the lower 20 + // bytes. This can always be done due to the 32-byte prefix. + + // The final memory read index is msg.data.length - 20 + 32 - 12 = msg.data.length. Using inline assembly is the + // easiest/most-efficient way to perform this operation. + + // These fields are not accessible from assembly + bytes memory array = msg.data; + uint256 index = msg.data.length; + + // solhint-disable-next-line no-inline-assembly + assembly { + // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. + result := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + return result; + } +} From caf52fc1c5ac4122323be88ca5405453bee6ef79 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:32:25 -0600 Subject: [PATCH 08/33] add ERC2771 interfaces and storage library --- contracts/interfaces/IERC2771.sol | 9 ++++++++ contracts/interfaces/_IERC2771.sol | 5 +++++ contracts/storage/ERC2771Storage.sol | 31 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 contracts/interfaces/IERC2771.sol create mode 100644 contracts/interfaces/_IERC2771.sol create mode 100644 contracts/storage/ERC2771Storage.sol diff --git a/contracts/interfaces/IERC2771.sol b/contracts/interfaces/IERC2771.sol new file mode 100644 index 000000000..5a6a1826f --- /dev/null +++ b/contracts/interfaces/IERC2771.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { _IERC2771 } from './_IERC2771.sol'; + +interface IERC2771 is _IERC2771 { + function isTrustedForwarder(address forwarder) external view returns (bool); +} diff --git a/contracts/interfaces/_IERC2771.sol b/contracts/interfaces/_IERC2771.sol new file mode 100644 index 000000000..eecfcb360 --- /dev/null +++ b/contracts/interfaces/_IERC2771.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +interface _IERC2771 {} diff --git a/contracts/storage/ERC2771Storage.sol b/contracts/storage/ERC2771Storage.sol new file mode 100644 index 000000000..6b86c25de --- /dev/null +++ b/contracts/storage/ERC2771Storage.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +library ERC2771Storage { + /** + * @custom:storage-location erc7201:solidstate.contracts.storage.ERC2771 + */ + struct Layout { + mapping(address account => bool trustedStatus) trustedForwarders; + } + + bytes32 internal constant DEFAULT_STORAGE_SLOT = + keccak256( + abi.encode( + uint256( + keccak256(bytes('solidstate.contracts.storage.ERC2771')) + ) - 1 + ) + ) & ~bytes32(uint256(0xff)); + + function layout() internal pure returns (Layout storage $) { + $ = layout(DEFAULT_STORAGE_SLOT); + } + + function layout(bytes32 slot) internal pure returns (Layout storage $) { + assembly { + $.slot := slot + } + } +} From 93d35e2a21bfbc63b866f9b2642f922b35df5798 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:40:57 -0600 Subject: [PATCH 09/33] remove _getRelayedCallSender helper --- contracts/utils/_MetaTransactionContext.sol | 49 +++++++++------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_MetaTransactionContext.sol index 7fd07c295..0cf9f8f9b 100644 --- a/contracts/utils/_MetaTransactionContext.sol +++ b/contracts/utils/_MetaTransactionContext.sol @@ -21,38 +21,29 @@ abstract contract _MetaTransactionContext is _Context { returns (address msgSender) { if (msg.sender == address(this)) { - msgSender = _getRelayedCallSender(); - } else { - msgSender = msg.sender; - } - } + // We need to read 20 bytes (an address) located at array index msg.data.length - 20. In memory, the array + // is prefixed with a 32-byte length value, so we first add 32 to get the memory read index. However, doing + // so would leave the address in the upper 20 bytes of the 32-byte word, which is inconvenient and would + // require bit shifting. We therefore subtract 12 from the read index so the address lands on the lower 20 + // bytes. This can always be done due to the 32-byte prefix. - function _getRelayedCallSender() - private - pure - returns (address payable result) - { - // We need to read 20 bytes (an address) located at array index msg.data.length - 20. In memory, the array - // is prefixed with a 32-byte length value, so we first add 32 to get the memory read index. However, doing - // so would leave the address in the upper 20 bytes of the 32-byte word, which is inconvenient and would - // require bit shifting. We therefore subtract 12 from the read index so the address lands on the lower 20 - // bytes. This can always be done due to the 32-byte prefix. - - // The final memory read index is msg.data.length - 20 + 32 - 12 = msg.data.length. Using inline assembly is the - // easiest/most-efficient way to perform this operation. + // The final memory read index is msg.data.length - 20 + 32 - 12 = msg.data.length. Using inline assembly is the + // easiest/most-efficient way to perform this operation. - // These fields are not accessible from assembly - bytes memory array = msg.data; - uint256 index = msg.data.length; + // These fields are not accessible from assembly + bytes memory array = msg.data; + uint256 index = msg.data.length; - // solhint-disable-next-line no-inline-assembly - assembly { - // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. - result := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) + // solhint-disable-next-line no-inline-assembly + assembly { + // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. + msgSender := and( + mload(add(array, index)), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } else { + msgSender = msg.sender; } - return result; } } From 645bcb4b25792258a00440009148b65ccbc80721 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:54:18 -0600 Subject: [PATCH 10/33] add msg.data processing functions to Context --- contracts/utils/_Context.sol | 13 +++++++++++ contracts/utils/_MetaTransactionContext.sol | 24 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/contracts/utils/_Context.sol b/contracts/utils/_Context.sol index dd8272844..739d5c987 100644 --- a/contracts/utils/_Context.sol +++ b/contracts/utils/_Context.sol @@ -10,4 +10,17 @@ abstract contract _Context is _IContext { function _msgSender() internal view virtual returns (address msgSender) { msgSender = msg.sender; } + + function _msgData() internal view virtual returns (bytes calldata msgData) { + msgData = msg.data; + } + + function _calldataSuffixLength() + internal + view + virtual + returns (uint256 length) + { + length = 0; + } } diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_MetaTransactionContext.sol index 0cf9f8f9b..e59cc8b79 100644 --- a/contracts/utils/_MetaTransactionContext.sol +++ b/contracts/utils/_MetaTransactionContext.sol @@ -46,4 +46,28 @@ abstract contract _MetaTransactionContext is _Context { msgSender = msg.sender; } } + + function _msgData() + internal + view + virtual + override + returns (bytes calldata msgData) + { + if (msg.sender == address(this)) { + msgData = msg.data[:msg.data.length - _calldataSuffixLength()]; + } else { + msgData = msg.data; + } + } + + function _calldataSuffixLength() + internal + view + virtual + override + returns (uint256 length) + { + length = 20; + } } From df41fde45a1cd03c72144a079e5d17f54060e7b1 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:56:04 -0600 Subject: [PATCH 11/33] use super calls to Context --- contracts/utils/_MetaTransactionContext.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_MetaTransactionContext.sol index e59cc8b79..911ebaf4b 100644 --- a/contracts/utils/_MetaTransactionContext.sol +++ b/contracts/utils/_MetaTransactionContext.sol @@ -43,7 +43,7 @@ abstract contract _MetaTransactionContext is _Context { ) } } else { - msgSender = msg.sender; + msgSender = super._msgSender(); } } @@ -57,7 +57,7 @@ abstract contract _MetaTransactionContext is _Context { if (msg.sender == address(this)) { msgData = msg.data[:msg.data.length - _calldataSuffixLength()]; } else { - msgData = msg.data; + msgData = super._msgData(); } } From 530275805bce42b4e4f408b36d50f084fc3d3bb5 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:56:56 -0600 Subject: [PATCH 12/33] remove Context comments --- contracts/utils/_MetaTransactionContext.sol | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_MetaTransactionContext.sol index 911ebaf4b..b91e87e79 100644 --- a/contracts/utils/_MetaTransactionContext.sol +++ b/contracts/utils/_MetaTransactionContext.sol @@ -5,14 +5,6 @@ pragma solidity ^0.8.20; import { _Context } from './_Context.sol'; abstract contract _MetaTransactionContext is _Context { - /* - * @notice Returns the intended sender of a message. Either msg.sender or the address of the authorizing signer. - * Enables MetaTransactions, since the sender doesn't need to be the tx.origin or even the msg.sender. - * @returns address - The account whose authority is being acted on. - * and the end-user for GSN relayed calls (where msg.sender is actually `RelayHub`). - * - * IMPORTANT: Contracts derived from {GSNRecipient} should never use `msg.sender`, and use {_msgSender} instead. - */ function _msgSender() internal view @@ -21,22 +13,10 @@ abstract contract _MetaTransactionContext is _Context { returns (address msgSender) { if (msg.sender == address(this)) { - // We need to read 20 bytes (an address) located at array index msg.data.length - 20. In memory, the array - // is prefixed with a 32-byte length value, so we first add 32 to get the memory read index. However, doing - // so would leave the address in the upper 20 bytes of the 32-byte word, which is inconvenient and would - // require bit shifting. We therefore subtract 12 from the read index so the address lands on the lower 20 - // bytes. This can always be done due to the 32-byte prefix. - - // The final memory read index is msg.data.length - 20 + 32 - 12 = msg.data.length. Using inline assembly is the - // easiest/most-efficient way to perform this operation. - - // These fields are not accessible from assembly bytes memory array = msg.data; uint256 index = msg.data.length; - // solhint-disable-next-line no-inline-assembly assembly { - // Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. msgSender := and( mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff From 4fd84f17023589964ed9f005b74d0c8825f0fe16 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 13:59:16 -0600 Subject: [PATCH 13/33] remove assembly in _msgSender --- contracts/utils/_MetaTransactionContext.sol | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_MetaTransactionContext.sol index b91e87e79..a90ed06d9 100644 --- a/contracts/utils/_MetaTransactionContext.sol +++ b/contracts/utils/_MetaTransactionContext.sol @@ -13,15 +13,9 @@ abstract contract _MetaTransactionContext is _Context { returns (address msgSender) { if (msg.sender == address(this)) { - bytes memory array = msg.data; - uint256 index = msg.data.length; - - assembly { - msgSender := and( - mload(add(array, index)), - 0xffffffffffffffffffffffffffffffffffffffff - ) - } + msgSender = address( + bytes20(msg.data[msg.data.length - _calldataSuffixLength():]) + ); } else { msgSender = super._msgSender(); } From 78c875d10e3069997ec841424aad62f92423331c Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 14:08:42 -0600 Subject: [PATCH 14/33] add Context comments --- contracts/utils/_Context.sol | 16 ++++++++++++++++ contracts/utils/_MetaTransactionContext.sol | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/contracts/utils/_Context.sol b/contracts/utils/_Context.sol index 739d5c987..303eab6c6 100644 --- a/contracts/utils/_Context.sol +++ b/contracts/utils/_Context.sol @@ -3,18 +3,34 @@ pragma solidity ^0.8.20; import { _IContext } from './_IContext.sol'; + /** * @title Utility contract for supporting alternative authorization schemes */ abstract contract _Context is _IContext { + /** + * @notice return the message sender + * @dev if no Context extension is in use, msg.sender is returned as-is + * @return msgSender account contextualized as message sender + */ function _msgSender() internal view virtual returns (address msgSender) { msgSender = msg.sender; } + /** + * @notice return the message data + * @dev if no Context extension is in use, msg.data is returned as-is + * @return msgData message data with suffix removed, if applicable + */ function _msgData() internal view virtual returns (bytes calldata msgData) { msgData = msg.data; } + /** + * @notice return the bytes length of the calldata context suffix + * @dev if no Context extension is in use, suffix length is 0 + * @return length length of calldata context suffix + */ function _calldataSuffixLength() internal view diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_MetaTransactionContext.sol index a90ed06d9..b1d99e326 100644 --- a/contracts/utils/_MetaTransactionContext.sol +++ b/contracts/utils/_MetaTransactionContext.sol @@ -5,6 +5,10 @@ pragma solidity ^0.8.20; import { _Context } from './_Context.sol'; abstract contract _MetaTransactionContext is _Context { + /** + * @inheritdoc _Context + * @dev sender is read from the calldata context suffix + */ function _msgSender() internal view @@ -21,6 +25,9 @@ abstract contract _MetaTransactionContext is _Context { } } + /** + * @inheritdoc _Context + */ function _msgData() internal view @@ -35,6 +42,10 @@ abstract contract _MetaTransactionContext is _Context { } } + /** + * @inheritdoc _Context + * @dev this Context extension defines an address suffix with a length of 20 + */ function _calldataSuffixLength() internal view From 588aea2dfa8b4fc10f71a57d3dd728bfa44e2bb1 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 14:11:11 -0600 Subject: [PATCH 15/33] rename MetaTransactionContext to ForwardedMetaTransactionContext --- ...ansactionContext.sol => _ForwardedMetaTransationContext.sol} | 2 +- ...nsactionContext.sol => _IForwardedMetaTransationContext.sol} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename contracts/utils/{_MetaTransactionContext.sol => _ForwardedMetaTransationContext.sol} (95%) rename contracts/utils/{_IMetaTransactionContext.sol => _IForwardedMetaTransationContext.sol} (64%) diff --git a/contracts/utils/_MetaTransactionContext.sol b/contracts/utils/_ForwardedMetaTransationContext.sol similarity index 95% rename from contracts/utils/_MetaTransactionContext.sol rename to contracts/utils/_ForwardedMetaTransationContext.sol index b1d99e326..e5f1b67c9 100644 --- a/contracts/utils/_MetaTransactionContext.sol +++ b/contracts/utils/_ForwardedMetaTransationContext.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import { _Context } from './_Context.sol'; -abstract contract _MetaTransactionContext is _Context { +abstract contract _ForwardedMetaTransationContext is _Context { /** * @inheritdoc _Context * @dev sender is read from the calldata context suffix diff --git a/contracts/utils/_IMetaTransactionContext.sol b/contracts/utils/_IForwardedMetaTransationContext.sol similarity index 64% rename from contracts/utils/_IMetaTransactionContext.sol rename to contracts/utils/_IForwardedMetaTransationContext.sol index 3c4a8af2a..4b63e80d7 100644 --- a/contracts/utils/_IMetaTransactionContext.sol +++ b/contracts/utils/_IForwardedMetaTransationContext.sol @@ -4,4 +4,4 @@ pragma solidity ^0.8.20; import { _IContext } from './_IContext.sol'; -interface _IMetaTransactionContext is _IContext {} +interface _IForwardedMetaTransationContext is _IContext {} From e13a3e29d60d77577fd38e52d7e8f481129adfe7 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 14:20:49 -0600 Subject: [PATCH 16/33] conform ForwardedMetaTransactionContext to ERC2771 --- .../utils/ForwardedMetaTransationContext.sol | 17 ++++++++++++++++ .../utils/IForwardedMetaTransationContext.sol | 11 ++++++++++ .../utils/_ForwardedMetaTransationContext.sol | 20 ++++++++++++++++--- .../_IForwardedMetaTransationContext.sol | 3 ++- 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 contracts/utils/ForwardedMetaTransationContext.sol create mode 100644 contracts/utils/IForwardedMetaTransationContext.sol diff --git a/contracts/utils/ForwardedMetaTransationContext.sol b/contracts/utils/ForwardedMetaTransationContext.sol new file mode 100644 index 000000000..5a232a78b --- /dev/null +++ b/contracts/utils/ForwardedMetaTransationContext.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IForwardedMetaTransationContext } from './IForwardedMetaTransationContext.sol'; +import { _ForwardedMetaTransationContext } from './_ForwardedMetaTransationContext.sol'; + +abstract contract ForwardedMetaTransationContext is + IForwardedMetaTransationContext, + _ForwardedMetaTransationContext +{ + function isTrustedForwarder( + address account + ) external view returns (bool trustedStatus) { + trustedStatus = _isTrustedForwarder(account); + } +} diff --git a/contracts/utils/IForwardedMetaTransationContext.sol b/contracts/utils/IForwardedMetaTransationContext.sol new file mode 100644 index 000000000..ac7c108fa --- /dev/null +++ b/contracts/utils/IForwardedMetaTransationContext.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IERC2771 } from '../interfaces/IERC2771.sol'; +import { _IForwardedMetaTransationContext } from './_IForwardedMetaTransationContext.sol'; + +interface IForwardedMetaTransationContext is + _IForwardedMetaTransationContext, + IERC2771 +{} diff --git a/contracts/utils/_ForwardedMetaTransationContext.sol b/contracts/utils/_ForwardedMetaTransationContext.sol index e5f1b67c9..aa0fba912 100644 --- a/contracts/utils/_ForwardedMetaTransationContext.sol +++ b/contracts/utils/_ForwardedMetaTransationContext.sol @@ -2,9 +2,14 @@ pragma solidity ^0.8.20; +import { ERC2771Storage } from '../storage/ERC2771Storage.sol'; import { _Context } from './_Context.sol'; +import { _IForwardedMetaTransationContext } from './_IForwardedMetaTransationContext.sol'; -abstract contract _ForwardedMetaTransationContext is _Context { +abstract contract _ForwardedMetaTransationContext is + _IForwardedMetaTransationContext, + _Context +{ /** * @inheritdoc _Context * @dev sender is read from the calldata context suffix @@ -16,7 +21,7 @@ abstract contract _ForwardedMetaTransationContext is _Context { override returns (address msgSender) { - if (msg.sender == address(this)) { + if (_isTrustedForwarder(msg.sender)) { msgSender = address( bytes20(msg.data[msg.data.length - _calldataSuffixLength():]) ); @@ -35,7 +40,7 @@ abstract contract _ForwardedMetaTransationContext is _Context { override returns (bytes calldata msgData) { - if (msg.sender == address(this)) { + if (_isTrustedForwarder(msg.sender)) { msgData = msg.data[:msg.data.length - _calldataSuffixLength()]; } else { msgData = super._msgData(); @@ -55,4 +60,13 @@ abstract contract _ForwardedMetaTransationContext is _Context { { length = 20; } + + function _isTrustedForwarder( + address account + ) internal view virtual returns (bool trustedStatus) { + return + ERC2771Storage + .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) + .trustedForwarders[account]; + } } diff --git a/contracts/utils/_IForwardedMetaTransationContext.sol b/contracts/utils/_IForwardedMetaTransationContext.sol index 4b63e80d7..f44c09c17 100644 --- a/contracts/utils/_IForwardedMetaTransationContext.sol +++ b/contracts/utils/_IForwardedMetaTransationContext.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; +import { _IERC2771 } from '../interfaces/_IERC2771.sol'; import { _IContext } from './_IContext.sol'; -interface _IForwardedMetaTransationContext is _IContext {} +interface _IForwardedMetaTransationContext is _IContext, _IERC2771 {} From d71154c35680bf6a73c9b1013562dc404e0bb532 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 14:25:32 -0600 Subject: [PATCH 17/33] add comments to IERC2771 --- contracts/interfaces/IERC2771.sol | 13 ++++++++++++- contracts/utils/ForwardedMetaTransationContext.sol | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IERC2771.sol b/contracts/interfaces/IERC2771.sol index 5a6a1826f..ead7e2f2f 100644 --- a/contracts/interfaces/IERC2771.sol +++ b/contracts/interfaces/IERC2771.sol @@ -4,6 +4,17 @@ pragma solidity ^0.8.20; import { _IERC2771 } from './_IERC2771.sol'; +/** + * @title ERC2771 interface + * @dev see https://eips.ethereum.org/EIPS/eip-2771 + */ interface IERC2771 is _IERC2771 { - function isTrustedForwarder(address forwarder) external view returns (bool); + /** + * @notice query whether account is a trusted ERC2771 forwarder + * @param account address to query + * @return trustedStatus whether account is a trusted forwarder + */ + function isTrustedForwarder( + address account + ) external view returns (bool trustedStatus); } diff --git a/contracts/utils/ForwardedMetaTransationContext.sol b/contracts/utils/ForwardedMetaTransationContext.sol index 5a232a78b..fdbdcb51b 100644 --- a/contracts/utils/ForwardedMetaTransationContext.sol +++ b/contracts/utils/ForwardedMetaTransationContext.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; +import { IERC2771 } from '../interfaces/IERC2771.sol'; import { IForwardedMetaTransationContext } from './IForwardedMetaTransationContext.sol'; import { _ForwardedMetaTransationContext } from './_ForwardedMetaTransationContext.sol'; @@ -9,6 +10,9 @@ abstract contract ForwardedMetaTransationContext is IForwardedMetaTransationContext, _ForwardedMetaTransationContext { + /** + * @inheritdoc IERC2771 + */ function isTrustedForwarder( address account ) external view returns (bool trustedStatus) { From 1cb92ff4fe081cc7800fc73a3a592fcc0d6d1663 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 14:44:24 -0600 Subject: [PATCH 18/33] check data length before returning context --- .../utils/_ForwardedMetaTransationContext.sol | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/contracts/utils/_ForwardedMetaTransationContext.sol b/contracts/utils/_ForwardedMetaTransationContext.sol index aa0fba912..f1858b5c9 100644 --- a/contracts/utils/_ForwardedMetaTransationContext.sol +++ b/contracts/utils/_ForwardedMetaTransationContext.sol @@ -21,10 +21,15 @@ abstract contract _ForwardedMetaTransationContext is override returns (address msgSender) { - if (_isTrustedForwarder(msg.sender)) { - msgSender = address( - bytes20(msg.data[msg.data.length - _calldataSuffixLength():]) - ); + uint256 dataLength = msg.data.length; + uint256 suffixLength = _calldataSuffixLength(); + + if (dataLength >= suffixLength && _isTrustedForwarder(msg.sender)) { + unchecked { + msgSender = address( + bytes20(msg.data[dataLength - suffixLength:]) + ); + } } else { msgSender = super._msgSender(); } @@ -40,8 +45,13 @@ abstract contract _ForwardedMetaTransationContext is override returns (bytes calldata msgData) { - if (_isTrustedForwarder(msg.sender)) { - msgData = msg.data[:msg.data.length - _calldataSuffixLength()]; + uint256 dataLength = msg.data.length; + uint256 suffixLength = _calldataSuffixLength(); + + if (dataLength >= suffixLength && _isTrustedForwarder(msg.sender)) { + unchecked { + msgData = msg.data[:dataLength - suffixLength]; + } } else { msgData = super._msgData(); } From 0466d01f9f575db3b3811550d5f5429a74ecab71 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 15:47:13 -0600 Subject: [PATCH 19/33] add note about non-revert case and NatSpec comments --- contracts/utils/_ForwardedMetaTransationContext.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/contracts/utils/_ForwardedMetaTransationContext.sol b/contracts/utils/_ForwardedMetaTransationContext.sol index f1858b5c9..59bfd4565 100644 --- a/contracts/utils/_ForwardedMetaTransationContext.sol +++ b/contracts/utils/_ForwardedMetaTransationContext.sol @@ -24,6 +24,9 @@ abstract contract _ForwardedMetaTransationContext is uint256 dataLength = msg.data.length; uint256 suffixLength = _calldataSuffixLength(); + // ideally this would revert when dataLength < suffixLength and sender is a trusted forwarder + // but because _isTrustedForwarder reads from storage, it should be called only when necessary + if (dataLength >= suffixLength && _isTrustedForwarder(msg.sender)) { unchecked { msgSender = address( @@ -48,6 +51,9 @@ abstract contract _ForwardedMetaTransationContext is uint256 dataLength = msg.data.length; uint256 suffixLength = _calldataSuffixLength(); + // ideally this would revert when dataLength < suffixLength and sender is a trusted forwarder + // but because _isTrustedForwarder reads from storage, it should be called only when necessary + if (dataLength >= suffixLength && _isTrustedForwarder(msg.sender)) { unchecked { msgData = msg.data[:dataLength - suffixLength]; @@ -71,6 +77,11 @@ abstract contract _ForwardedMetaTransationContext is length = 20; } + /** + * @notice query whether account is a trusted ERC2771 forwarder + * @param account address to query + * @return trustedStatus whether account is a trusted forwarder + */ function _isTrustedForwarder( address account ) internal view virtual returns (bool trustedStatus) { From 9290760eb7e9177c3434b8873c619f04a6717723 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 19:26:59 -0600 Subject: [PATCH 20/33] consider only contracts to be trusted forwarders --- contracts/utils/_ForwardedMetaTransationContext.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/utils/_ForwardedMetaTransationContext.sol b/contracts/utils/_ForwardedMetaTransationContext.sol index 59bfd4565..783da7f9b 100644 --- a/contracts/utils/_ForwardedMetaTransationContext.sol +++ b/contracts/utils/_ForwardedMetaTransationContext.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import { ERC2771Storage } from '../storage/ERC2771Storage.sol'; +import { AddressUtils } from './AddressUtils.sol'; import { _Context } from './_Context.sol'; import { _IForwardedMetaTransationContext } from './_IForwardedMetaTransationContext.sol'; @@ -10,6 +11,8 @@ abstract contract _ForwardedMetaTransationContext is _IForwardedMetaTransationContext, _Context { + using AddressUtils for address; + /** * @inheritdoc _Context * @dev sender is read from the calldata context suffix @@ -79,13 +82,15 @@ abstract contract _ForwardedMetaTransationContext is /** * @notice query whether account is a trusted ERC2771 forwarder + * @dev only contracts can be considered trusted forwarders * @param account address to query * @return trustedStatus whether account is a trusted forwarder */ function _isTrustedForwarder( address account ) internal view virtual returns (bool trustedStatus) { - return + trustedStatus = + account.isContract() && ERC2771Storage .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) .trustedForwarders[account]; From 26c7c1b5009f7528b98e360e024d5b27d827e946 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 19:45:07 -0600 Subject: [PATCH 21/33] add trusted forwarder status accessors --- .../utils/_ForwardedMetaTransationContext.sol | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/contracts/utils/_ForwardedMetaTransationContext.sol b/contracts/utils/_ForwardedMetaTransationContext.sol index 783da7f9b..998af3fb8 100644 --- a/contracts/utils/_ForwardedMetaTransationContext.sol +++ b/contracts/utils/_ForwardedMetaTransationContext.sol @@ -13,6 +13,8 @@ abstract contract _ForwardedMetaTransationContext is { using AddressUtils for address; + error ForwardedMetaTransactionContext__TrustedForwarderMustBeContract(); + /** * @inheritdoc _Context * @dev sender is read from the calldata context suffix @@ -95,4 +97,28 @@ abstract contract _ForwardedMetaTransationContext is .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) .trustedForwarders[account]; } + + /** + * @notice grant trusted forwarder status to account + * @param account account whose trusted forwarder status to grant + */ + function _addTrustedForwarder(address account) internal virtual { + // exception for address(this) allows a contract to set itself as a trusted forwarder in its constructor + if (!account.isContract() && account != address(this)) + revert ForwardedMetaTransactionContext__TrustedForwarderMustBeContract(); + + ERC2771Storage + .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) + .trustedForwarders[account] = true; + } + + /** + * @notice revoke trusted forwarder status from account + * @param account account whose trusted forwarder status to revoke + */ + function _removeTrustedForwarder(address account) internal virtual { + delete ERC2771Storage + .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) + .trustedForwarders[account]; + } } From 3904c441f42ca1b3689941ffaecff44f5844c104 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 21:40:51 -0600 Subject: [PATCH 22/33] add external Context contract --- contracts/utils/Context.sol | 8 ++++ .../utils/ForwardedMetaTransationContext.sol | 44 ++++++++++++++++++- contracts/utils/IContext.sol | 7 +++ .../utils/IForwardedMetaTransationContext.sol | 2 + 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 contracts/utils/Context.sol create mode 100644 contracts/utils/IContext.sol diff --git a/contracts/utils/Context.sol b/contracts/utils/Context.sol new file mode 100644 index 000000000..4b71c514a --- /dev/null +++ b/contracts/utils/Context.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IContext } from './IContext.sol'; +import { _Context } from './_Context.sol'; + +abstract contract Context is IContext, _Context {} diff --git a/contracts/utils/ForwardedMetaTransationContext.sol b/contracts/utils/ForwardedMetaTransationContext.sol index fdbdcb51b..212fab75a 100644 --- a/contracts/utils/ForwardedMetaTransationContext.sol +++ b/contracts/utils/ForwardedMetaTransationContext.sol @@ -3,12 +3,15 @@ pragma solidity ^0.8.20; import { IERC2771 } from '../interfaces/IERC2771.sol'; +import { Context } from './Context.sol'; +import { _Context } from './_Context.sol'; import { IForwardedMetaTransationContext } from './IForwardedMetaTransationContext.sol'; import { _ForwardedMetaTransationContext } from './_ForwardedMetaTransationContext.sol'; abstract contract ForwardedMetaTransationContext is IForwardedMetaTransationContext, - _ForwardedMetaTransationContext + _ForwardedMetaTransationContext, + Context { /** * @inheritdoc IERC2771 @@ -18,4 +21,43 @@ abstract contract ForwardedMetaTransationContext is ) external view returns (bool trustedStatus) { trustedStatus = _isTrustedForwarder(account); } + + /** + * @inheritdoc _ForwardedMetaTransationContext + */ + function _msgSender() + internal + view + virtual + override(_Context, _ForwardedMetaTransationContext) + returns (address msgSender) + { + msgSender = super._msgSender(); + } + + /** + * @inheritdoc _ForwardedMetaTransationContext + */ + function _msgData() + internal + view + virtual + override(_Context, _ForwardedMetaTransationContext) + returns (bytes calldata msgData) + { + msgData = super._msgData(); + } + + /** + * @inheritdoc _ForwardedMetaTransationContext + */ + function _calldataSuffixLength() + internal + view + virtual + override(_Context, _ForwardedMetaTransationContext) + returns (uint256 length) + { + length = super._calldataSuffixLength(); + } } diff --git a/contracts/utils/IContext.sol b/contracts/utils/IContext.sol new file mode 100644 index 000000000..8cfd70e11 --- /dev/null +++ b/contracts/utils/IContext.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { _IContext } from './_IContext.sol'; + +interface IContext is _IContext {} diff --git a/contracts/utils/IForwardedMetaTransationContext.sol b/contracts/utils/IForwardedMetaTransationContext.sol index ac7c108fa..d9d760be4 100644 --- a/contracts/utils/IForwardedMetaTransationContext.sol +++ b/contracts/utils/IForwardedMetaTransationContext.sol @@ -3,9 +3,11 @@ pragma solidity ^0.8.20; import { IERC2771 } from '../interfaces/IERC2771.sol'; +import { IContext } from './IContext.sol'; import { _IForwardedMetaTransationContext } from './_IForwardedMetaTransationContext.sol'; interface IForwardedMetaTransationContext is _IForwardedMetaTransationContext, + IContext, IERC2771 {} From 93503008cc2f2bd3025b492e350ef42178db34fb Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 21:41:48 -0600 Subject: [PATCH 23/33] fix contract name --- ...ol => ForwardedMetaTransactionContext.sol} | 22 +++++++++---------- ...l => IForwardedMetaTransactionContext.sol} | 6 ++--- ...l => _ForwardedMetaTransactionContext.sol} | 6 ++--- ... => _IForwardedMetaTransactionContext.sol} | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) rename contracts/utils/{ForwardedMetaTransationContext.sol => ForwardedMetaTransactionContext.sol} (59%) rename contracts/utils/{IForwardedMetaTransationContext.sol => IForwardedMetaTransactionContext.sol} (51%) rename contracts/utils/{_ForwardedMetaTransationContext.sol => _ForwardedMetaTransactionContext.sol} (95%) rename contracts/utils/{_IForwardedMetaTransationContext.sol => _IForwardedMetaTransactionContext.sol} (69%) diff --git a/contracts/utils/ForwardedMetaTransationContext.sol b/contracts/utils/ForwardedMetaTransactionContext.sol similarity index 59% rename from contracts/utils/ForwardedMetaTransationContext.sol rename to contracts/utils/ForwardedMetaTransactionContext.sol index 212fab75a..7dc537add 100644 --- a/contracts/utils/ForwardedMetaTransationContext.sol +++ b/contracts/utils/ForwardedMetaTransactionContext.sol @@ -5,12 +5,12 @@ pragma solidity ^0.8.20; import { IERC2771 } from '../interfaces/IERC2771.sol'; import { Context } from './Context.sol'; import { _Context } from './_Context.sol'; -import { IForwardedMetaTransationContext } from './IForwardedMetaTransationContext.sol'; -import { _ForwardedMetaTransationContext } from './_ForwardedMetaTransationContext.sol'; +import { IForwardedMetaTransactionContext } from './IForwardedMetaTransactionContext.sol'; +import { _ForwardedMetaTransactionContext } from './_ForwardedMetaTransactionContext.sol'; -abstract contract ForwardedMetaTransationContext is - IForwardedMetaTransationContext, - _ForwardedMetaTransationContext, +abstract contract ForwardedMetaTransactionContext is + IForwardedMetaTransactionContext, + _ForwardedMetaTransactionContext, Context { /** @@ -23,39 +23,39 @@ abstract contract ForwardedMetaTransationContext is } /** - * @inheritdoc _ForwardedMetaTransationContext + * @inheritdoc _ForwardedMetaTransactionContext */ function _msgSender() internal view virtual - override(_Context, _ForwardedMetaTransationContext) + override(_Context, _ForwardedMetaTransactionContext) returns (address msgSender) { msgSender = super._msgSender(); } /** - * @inheritdoc _ForwardedMetaTransationContext + * @inheritdoc _ForwardedMetaTransactionContext */ function _msgData() internal view virtual - override(_Context, _ForwardedMetaTransationContext) + override(_Context, _ForwardedMetaTransactionContext) returns (bytes calldata msgData) { msgData = super._msgData(); } /** - * @inheritdoc _ForwardedMetaTransationContext + * @inheritdoc _ForwardedMetaTransactionContext */ function _calldataSuffixLength() internal view virtual - override(_Context, _ForwardedMetaTransationContext) + override(_Context, _ForwardedMetaTransactionContext) returns (uint256 length) { length = super._calldataSuffixLength(); diff --git a/contracts/utils/IForwardedMetaTransationContext.sol b/contracts/utils/IForwardedMetaTransactionContext.sol similarity index 51% rename from contracts/utils/IForwardedMetaTransationContext.sol rename to contracts/utils/IForwardedMetaTransactionContext.sol index d9d760be4..79e832739 100644 --- a/contracts/utils/IForwardedMetaTransationContext.sol +++ b/contracts/utils/IForwardedMetaTransactionContext.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.20; import { IERC2771 } from '../interfaces/IERC2771.sol'; import { IContext } from './IContext.sol'; -import { _IForwardedMetaTransationContext } from './_IForwardedMetaTransationContext.sol'; +import { _IForwardedMetaTransactionContext } from './_IForwardedMetaTransactionContext.sol'; -interface IForwardedMetaTransationContext is - _IForwardedMetaTransationContext, +interface IForwardedMetaTransactionContext is + _IForwardedMetaTransactionContext, IContext, IERC2771 {} diff --git a/contracts/utils/_ForwardedMetaTransationContext.sol b/contracts/utils/_ForwardedMetaTransactionContext.sol similarity index 95% rename from contracts/utils/_ForwardedMetaTransationContext.sol rename to contracts/utils/_ForwardedMetaTransactionContext.sol index 998af3fb8..ecfd74f1f 100644 --- a/contracts/utils/_ForwardedMetaTransationContext.sol +++ b/contracts/utils/_ForwardedMetaTransactionContext.sol @@ -5,10 +5,10 @@ pragma solidity ^0.8.20; import { ERC2771Storage } from '../storage/ERC2771Storage.sol'; import { AddressUtils } from './AddressUtils.sol'; import { _Context } from './_Context.sol'; -import { _IForwardedMetaTransationContext } from './_IForwardedMetaTransationContext.sol'; +import { _IForwardedMetaTransactionContext } from './_IForwardedMetaTransactionContext.sol'; -abstract contract _ForwardedMetaTransationContext is - _IForwardedMetaTransationContext, +abstract contract _ForwardedMetaTransactionContext is + _IForwardedMetaTransactionContext, _Context { using AddressUtils for address; diff --git a/contracts/utils/_IForwardedMetaTransationContext.sol b/contracts/utils/_IForwardedMetaTransactionContext.sol similarity index 69% rename from contracts/utils/_IForwardedMetaTransationContext.sol rename to contracts/utils/_IForwardedMetaTransactionContext.sol index f44c09c17..861b3f57e 100644 --- a/contracts/utils/_IForwardedMetaTransationContext.sol +++ b/contracts/utils/_IForwardedMetaTransactionContext.sol @@ -5,4 +5,4 @@ pragma solidity ^0.8.20; import { _IERC2771 } from '../interfaces/_IERC2771.sol'; import { _IContext } from './_IContext.sol'; -interface _IForwardedMetaTransationContext is _IContext, _IERC2771 {} +interface _IForwardedMetaTransactionContext is _IContext, _IERC2771 {} From 0d4aa5ac2b0e8f951a294caf38c9116d0ff9e2c0 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 22:25:34 -0600 Subject: [PATCH 24/33] test Context contract --- test/utils/Context.ts | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/utils/Context.ts diff --git a/test/utils/Context.ts b/test/utils/Context.ts new file mode 100644 index 000000000..3978cc5de --- /dev/null +++ b/test/utils/Context.ts @@ -0,0 +1,58 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { $Context, $Context__factory } from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +describe('Context', () => { + let instance: $Context; + let deployer: SignerWithAddress; + + beforeEach(async () => { + [deployer] = await ethers.getSigners(); + instance = await new $Context__factory(deployer).deploy(); + }); + + describe('__internal', () => { + describe('#_msgSender()', () => { + it('returns message sender', async () => { + const tx = await instance.$_msgSender.populateTransaction(); + + tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); + + const result = await deployer.call(tx); + + const decoded = instance.interface.decodeFunctionResult( + '$_msgSender', + result, + ); + + expect(decoded).to.deep.equal([await deployer.getAddress()]); + }); + }); + + describe('#_msgData()', () => { + it('returns complete message data', async () => { + const tx = await instance.$_msgData.populateTransaction(); + + tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); + + // message data is returned as received, demonstrating the malleability of msg.data + + const result = await deployer.call(tx); + + const decoded = instance.interface.decodeFunctionResult( + '$_msgData', + result, + ); + + expect(decoded).to.deep.equal([tx.data]); + }); + }); + + describe('#_calldataSuffixLength()', () => { + it('returns 0', async () => { + expect(await instance.$_calldataSuffixLength.staticCall()).to.equal(0n); + }); + }); + }); +}); From 02f62f98c8e1c271ef842305fb4b284cbeecc6c0 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 22:47:19 -0600 Subject: [PATCH 25/33] test ForwardedMetaTransactionContext contract --- test/utils/Context.ts | 4 - test/utils/ForwardedMetaTransactionContext.ts | 154 ++++++++++++++++++ 2 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 test/utils/ForwardedMetaTransactionContext.ts diff --git a/test/utils/Context.ts b/test/utils/Context.ts index 3978cc5de..872a4f756 100644 --- a/test/utils/Context.ts +++ b/test/utils/Context.ts @@ -16,11 +16,9 @@ describe('Context', () => { describe('#_msgSender()', () => { it('returns message sender', async () => { const tx = await instance.$_msgSender.populateTransaction(); - tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); const result = await deployer.call(tx); - const decoded = instance.interface.decodeFunctionResult( '$_msgSender', result, @@ -33,13 +31,11 @@ describe('Context', () => { describe('#_msgData()', () => { it('returns complete message data', async () => { const tx = await instance.$_msgData.populateTransaction(); - tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); // message data is returned as received, demonstrating the malleability of msg.data const result = await deployer.call(tx); - const decoded = instance.interface.decodeFunctionResult( '$_msgData', result, diff --git a/test/utils/ForwardedMetaTransactionContext.ts b/test/utils/ForwardedMetaTransactionContext.ts new file mode 100644 index 000000000..67e2ac88f --- /dev/null +++ b/test/utils/ForwardedMetaTransactionContext.ts @@ -0,0 +1,154 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { + $ForwardedMetaTransactionContext, + $ForwardedMetaTransactionContext__factory, +} from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +describe('ForwardedMe$ForwardedMetaTransactionContext', () => { + let instance: $ForwardedMetaTransactionContext; + let deployer: SignerWithAddress; + + beforeEach(async () => { + [deployer] = await ethers.getSigners(); + instance = await new $ForwardedMetaTransactionContext__factory( + deployer, + ).deploy(); + }); + + // TODO: spec + + describe('__internal', () => { + describe('#_msgSender()', () => { + it('returns message sender is sender is not trusted forwarder', async () => { + const tx = await instance.$_msgSender.populateTransaction(); + tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); + + const result = await deployer.call(tx); + const decoded = instance.interface.decodeFunctionResult( + '$_msgSender', + result, + ); + + expect(decoded).to.deep.equal([await deployer.getAddress()]); + }); + + it('returns forwarded sender if sender is trusted forwarder', async () => { + const trustedForwarder = await ethers.getImpersonatedSigner( + await instance.getAddress(), + ); + await instance.$_addTrustedForwarder( + await trustedForwarder.getAddress(), + ); + + const forwardedAddress = ethers.hexlify(ethers.randomBytes(20)); + + const tx = await instance.$_msgSender.populateTransaction(); + tx.data = ethers.concat([tx.data, forwardedAddress]); + + const result = await trustedForwarder.call(tx); + const decoded = instance.interface.decodeFunctionResult( + '$_msgSender', + result, + ); + + expect(decoded).to.deep.equal([forwardedAddress]); + }); + }); + + describe('#_msgData()', () => { + it('returns complete message data if sender is not trusted forwarder', async () => { + const tx = await instance.$_msgData.populateTransaction(); + tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); + + // message data is returned as received, demonstrating the malleability of msg.data + + const result = await deployer.call(tx); + const decoded = instance.interface.decodeFunctionResult( + '$_msgData', + result, + ); + + expect(decoded).to.deep.equal([tx.data]); + }); + + it('returns message data without suffix if sender is trusted forwarder', async () => { + const trustedForwarder = await ethers.getImpersonatedSigner( + await instance.getAddress(), + ); + await instance.$_addTrustedForwarder( + await trustedForwarder.getAddress(), + ); + + const tx = await instance.$_msgData.populateTransaction(); + const nonSuffixedData = tx.data; + tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); + + // message data is returned as received, demonstrating the malleability of msg.data + + const result = await trustedForwarder.call(tx); + const decoded = instance.interface.decodeFunctionResult( + '$_msgData', + result, + ); + + expect(decoded).to.deep.equal([nonSuffixedData]); + }); + }); + + describe('#_calldataSuffixLength()', () => { + it('returns 20', async () => { + expect(await instance.$_calldataSuffixLength.staticCall()).to.equal( + 20n, + ); + }); + }); + + describe('#_isTrustedForwarder(address)', () => { + it('returns trusted forwarder status of account', async () => { + expect(await instance.$_isTrustedForwarder(await deployer.getAddress())) + .to.be.false; + expect(await instance.$_isTrustedForwarder(await instance.getAddress())) + .to.be.false; + + await instance.$_addTrustedForwarder(await instance.getAddress()); + + expect(await instance.$_isTrustedForwarder(await instance.getAddress())) + .to.be.true; + }); + }); + + describe('#_addTrustedForwarder(address)', () => { + it('grants trusted forwarder status to account', async () => { + await instance.$_addTrustedForwarder(await instance.getAddress()); + + expect(await instance.$_isTrustedForwarder(await instance.getAddress())) + .to.be.true; + }); + + describe('reverts if', () => { + it('account is not a contract', async () => { + // this is enforced via a code check + // there's an exception for address(this), but this is difficult to test here + await expect( + instance.$_addTrustedForwarder(ethers.ZeroAddress), + ).to.be.revertedWithCustomError( + instance, + 'ForwardedMetaTransactionContext__TrustedForwarderMustBeContract', + ); + }); + }); + }); + + describe('#_removeTrustedForwarder(address)', () => { + it('revokes trusted forwarder status from account', async () => { + await instance.$_addTrustedForwarder(await instance.getAddress()); + await instance.$_removeTrustedForwarder(await instance.getAddress()); + + expect(await instance.$_isTrustedForwarder(await instance.getAddress())) + .to.be.false; + }); + }); + }); +}); From c30c631a07748e97c08a84d0d3cecaf25f0a4579 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 18 Mar 2025 23:01:37 -0600 Subject: [PATCH 26/33] move context contracts to meta directory --- contracts/access/access_control/_AccessControl.sol | 2 +- contracts/access/access_control/_IAccessControl.sol | 2 +- contracts/access/ownable/_IOwnable.sol | 2 +- contracts/access/ownable/_Ownable.sol | 2 +- contracts/{utils => meta}/Context.sol | 0 contracts/{utils => meta}/ForwardedMetaTransactionContext.sol | 0 contracts/{utils => meta}/IContext.sol | 0 contracts/{utils => meta}/IForwardedMetaTransactionContext.sol | 0 contracts/{utils => meta}/_Context.sol | 0 contracts/{utils => meta}/_ForwardedMetaTransactionContext.sol | 2 +- contracts/{utils => meta}/_IContext.sol | 0 contracts/{utils => meta}/_IForwardedMetaTransactionContext.sol | 0 contracts/security/partially_pausable/_IPartiallyPausable.sol | 2 +- contracts/security/partially_pausable/_PartiallyPausable.sol | 2 +- contracts/security/pausable/_IPausable.sol | 2 +- contracts/security/pausable/_Pausable.sol | 2 +- contracts/token/fungible/_FungibleToken.sol | 2 +- contracts/token/fungible/_IFungibleToken.sol | 2 +- contracts/token/multi/_IMultiToken.sol | 2 +- contracts/token/multi/_MultiToken.sol | 2 +- contracts/token/non_fungible/_INonFungibleToken.sol | 2 +- contracts/token/non_fungible/_NonFungibleToken.sol | 2 +- 22 files changed, 15 insertions(+), 15 deletions(-) rename contracts/{utils => meta}/Context.sol (100%) rename contracts/{utils => meta}/ForwardedMetaTransactionContext.sol (100%) rename contracts/{utils => meta}/IContext.sol (100%) rename contracts/{utils => meta}/IForwardedMetaTransactionContext.sol (100%) rename contracts/{utils => meta}/_Context.sol (100%) rename contracts/{utils => meta}/_ForwardedMetaTransactionContext.sol (98%) rename contracts/{utils => meta}/_IContext.sol (100%) rename contracts/{utils => meta}/_IForwardedMetaTransactionContext.sol (100%) diff --git a/contracts/access/access_control/_AccessControl.sol b/contracts/access/access_control/_AccessControl.sol index adad27111..eb891a4ca 100644 --- a/contracts/access/access_control/_AccessControl.sol +++ b/contracts/access/access_control/_AccessControl.sol @@ -6,7 +6,7 @@ import { EnumerableSet } from '../../data/EnumerableSet.sol'; import { AccessControlStorage } from '../../storage/AccessControlStorage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; import { UintUtils } from '../../utils/UintUtils.sol'; -import { _Context } from '../../utils/_Context.sol'; +import { _Context } from '../../meta/_Context.sol'; import { _IAccessControl } from './_IAccessControl.sol'; /** diff --git a/contracts/access/access_control/_IAccessControl.sol b/contracts/access/access_control/_IAccessControl.sol index 0c089851c..2813d2480 100644 --- a/contracts/access/access_control/_IAccessControl.sol +++ b/contracts/access/access_control/_IAccessControl.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; -import { _IContext } from '../../utils/_IContext.sol'; +import { _IContext } from '../../meta/_IContext.sol'; /** * @title Partial AccessControl interface needed by internal functions diff --git a/contracts/access/ownable/_IOwnable.sol b/contracts/access/ownable/_IOwnable.sol index b3ec83597..e9c733804 100644 --- a/contracts/access/ownable/_IOwnable.sol +++ b/contracts/access/ownable/_IOwnable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import { _IERC173 } from '../../interfaces/_IERC173.sol'; -import { _IContext } from '../../utils/_IContext.sol'; +import { _IContext } from '../../meta/_IContext.sol'; interface _IOwnable is _IERC173, _IContext { error Ownable__NotOwner(); diff --git a/contracts/access/ownable/_Ownable.sol b/contracts/access/ownable/_Ownable.sol index d53c21a90..2f4250b4d 100644 --- a/contracts/access/ownable/_Ownable.sol +++ b/contracts/access/ownable/_Ownable.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; import { IERC173 } from '../../interfaces/IERC173.sol'; import { ERC173Storage } from '../../storage/ERC173Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { _Context } from '../../utils/_Context.sol'; +import { _Context } from '../../meta/_Context.sol'; import { _IOwnable } from './_IOwnable.sol'; abstract contract _Ownable is _IOwnable, _Context { diff --git a/contracts/utils/Context.sol b/contracts/meta/Context.sol similarity index 100% rename from contracts/utils/Context.sol rename to contracts/meta/Context.sol diff --git a/contracts/utils/ForwardedMetaTransactionContext.sol b/contracts/meta/ForwardedMetaTransactionContext.sol similarity index 100% rename from contracts/utils/ForwardedMetaTransactionContext.sol rename to contracts/meta/ForwardedMetaTransactionContext.sol diff --git a/contracts/utils/IContext.sol b/contracts/meta/IContext.sol similarity index 100% rename from contracts/utils/IContext.sol rename to contracts/meta/IContext.sol diff --git a/contracts/utils/IForwardedMetaTransactionContext.sol b/contracts/meta/IForwardedMetaTransactionContext.sol similarity index 100% rename from contracts/utils/IForwardedMetaTransactionContext.sol rename to contracts/meta/IForwardedMetaTransactionContext.sol diff --git a/contracts/utils/_Context.sol b/contracts/meta/_Context.sol similarity index 100% rename from contracts/utils/_Context.sol rename to contracts/meta/_Context.sol diff --git a/contracts/utils/_ForwardedMetaTransactionContext.sol b/contracts/meta/_ForwardedMetaTransactionContext.sol similarity index 98% rename from contracts/utils/_ForwardedMetaTransactionContext.sol rename to contracts/meta/_ForwardedMetaTransactionContext.sol index ecfd74f1f..2b645f386 100644 --- a/contracts/utils/_ForwardedMetaTransactionContext.sol +++ b/contracts/meta/_ForwardedMetaTransactionContext.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import { ERC2771Storage } from '../storage/ERC2771Storage.sol'; -import { AddressUtils } from './AddressUtils.sol'; +import { AddressUtils } from '../utils/AddressUtils.sol'; import { _Context } from './_Context.sol'; import { _IForwardedMetaTransactionContext } from './_IForwardedMetaTransactionContext.sol'; diff --git a/contracts/utils/_IContext.sol b/contracts/meta/_IContext.sol similarity index 100% rename from contracts/utils/_IContext.sol rename to contracts/meta/_IContext.sol diff --git a/contracts/utils/_IForwardedMetaTransactionContext.sol b/contracts/meta/_IForwardedMetaTransactionContext.sol similarity index 100% rename from contracts/utils/_IForwardedMetaTransactionContext.sol rename to contracts/meta/_IForwardedMetaTransactionContext.sol diff --git a/contracts/security/partially_pausable/_IPartiallyPausable.sol b/contracts/security/partially_pausable/_IPartiallyPausable.sol index 1705072c5..80a8fd8d1 100644 --- a/contracts/security/partially_pausable/_IPartiallyPausable.sol +++ b/contracts/security/partially_pausable/_IPartiallyPausable.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; -import { _IContext } from '../../utils/_IContext.sol'; +import { _IContext } from '../../meta/_IContext.sol'; interface _IPartiallyPausable is _IContext { error PartiallyPausable__PartiallyPaused(); diff --git a/contracts/security/partially_pausable/_PartiallyPausable.sol b/contracts/security/partially_pausable/_PartiallyPausable.sol index f510d1334..1d53b97ce 100644 --- a/contracts/security/partially_pausable/_PartiallyPausable.sol +++ b/contracts/security/partially_pausable/_PartiallyPausable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import { PausableStorage } from '../../storage/PausableStorage.sol'; -import { _Context } from '../../utils/_Context.sol'; +import { _Context } from '../../meta/_Context.sol'; import { _IPartiallyPausable } from './_IPartiallyPausable.sol'; /** diff --git a/contracts/security/pausable/_IPausable.sol b/contracts/security/pausable/_IPausable.sol index 1c2dca946..d2eba9a27 100644 --- a/contracts/security/pausable/_IPausable.sol +++ b/contracts/security/pausable/_IPausable.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; -import { _IContext } from '../../utils/_IContext.sol'; +import { _IContext } from '../../meta/_IContext.sol'; interface _IPausable is _IContext { error Pausable__Paused(); diff --git a/contracts/security/pausable/_Pausable.sol b/contracts/security/pausable/_Pausable.sol index 2e6bf39da..5ee1e7433 100644 --- a/contracts/security/pausable/_Pausable.sol +++ b/contracts/security/pausable/_Pausable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import { PausableStorage } from '../../storage/PausableStorage.sol'; -import { _Context } from '../../utils/_Context.sol'; +import { _Context } from '../../meta/_Context.sol'; import { _IPausable } from './_IPausable.sol'; /** diff --git a/contracts/token/fungible/_FungibleToken.sol b/contracts/token/fungible/_FungibleToken.sol index 440b5ae81..73a6c4ace 100644 --- a/contracts/token/fungible/_FungibleToken.sol +++ b/contracts/token/fungible/_FungibleToken.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import { ERC20Storage } from '../../storage/ERC20Storage.sol'; -import { _Context } from '../../utils/_Context.sol'; +import { _Context } from '../../meta/_Context.sol'; import { _IFungibleToken } from './_IFungibleToken.sol'; /** diff --git a/contracts/token/fungible/_IFungibleToken.sol b/contracts/token/fungible/_IFungibleToken.sol index 0757ff671..64cbc1629 100644 --- a/contracts/token/fungible/_IFungibleToken.sol +++ b/contracts/token/fungible/_IFungibleToken.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import { _IERC20 } from '../../interfaces/_IERC20.sol'; -import { _IContext } from '../../utils/_IContext.sol'; +import { _IContext } from '../../meta/_IContext.sol'; /** * @title FungibleToken base interface diff --git a/contracts/token/multi/_IMultiToken.sol b/contracts/token/multi/_IMultiToken.sol index 214bc256e..c81d4cb29 100644 --- a/contracts/token/multi/_IMultiToken.sol +++ b/contracts/token/multi/_IMultiToken.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import { _IERC1155 } from '../../interfaces/_IERC1155.sol'; import { _IIntrospectable } from '../../introspection/_IIntrospectable.sol'; -import { _IContext } from '../../utils/_IContext.sol'; +import { _IContext } from '../../meta/_IContext.sol'; /** * @title MultiToken base interface diff --git a/contracts/token/multi/_MultiToken.sol b/contracts/token/multi/_MultiToken.sol index cbffd0b3f..dac82e60c 100644 --- a/contracts/token/multi/_MultiToken.sol +++ b/contracts/token/multi/_MultiToken.sol @@ -6,7 +6,7 @@ import { IERC1155Receiver } from '../../interfaces/IERC1155Receiver.sol'; import { _Introspectable } from '../../introspection/_Introspectable.sol'; import { ERC1155Storage } from '../../storage/ERC1155Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { _Context } from '../../utils/_Context.sol'; +import { _Context } from '../../meta/_Context.sol'; import { _IMultiToken } from './_IMultiToken.sol'; /** diff --git a/contracts/token/non_fungible/_INonFungibleToken.sol b/contracts/token/non_fungible/_INonFungibleToken.sol index bc4de714a..bbc762683 100644 --- a/contracts/token/non_fungible/_INonFungibleToken.sol +++ b/contracts/token/non_fungible/_INonFungibleToken.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import { _IERC721 } from '../../interfaces/_IERC721.sol'; import { _IIntrospectable } from '../../introspection/_IIntrospectable.sol'; -import { _IContext } from '../../utils/_IContext.sol'; +import { _IContext } from '../../meta/_IContext.sol'; /** * @title NonFungibleToken base interface diff --git a/contracts/token/non_fungible/_NonFungibleToken.sol b/contracts/token/non_fungible/_NonFungibleToken.sol index 899d84240..38cff59ae 100644 --- a/contracts/token/non_fungible/_NonFungibleToken.sol +++ b/contracts/token/non_fungible/_NonFungibleToken.sol @@ -8,7 +8,7 @@ import { IERC721Receiver } from '../../interfaces/IERC721Receiver.sol'; import { _Introspectable } from '../../introspection/_Introspectable.sol'; import { ERC721Storage } from '../../storage/ERC721Storage.sol'; import { AddressUtils } from '../../utils/AddressUtils.sol'; -import { _Context } from '../../utils/_Context.sol'; +import { _Context } from '../../meta/_Context.sol'; import { _INonFungibleToken } from './_INonFungibleToken.sol'; /** From 3c3a05d38aba142001e3edabe9b959c4511d86ee Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 10:38:56 -0600 Subject: [PATCH 27/33] move context tests to meta directory --- test/{utils => meta}/Context.ts | 0 test/{utils => meta}/ForwardedMetaTransactionContext.ts | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/{utils => meta}/Context.ts (100%) rename test/{utils => meta}/ForwardedMetaTransactionContext.ts (100%) diff --git a/test/utils/Context.ts b/test/meta/Context.ts similarity index 100% rename from test/utils/Context.ts rename to test/meta/Context.ts diff --git a/test/utils/ForwardedMetaTransactionContext.ts b/test/meta/ForwardedMetaTransactionContext.ts similarity index 100% rename from test/utils/ForwardedMetaTransactionContext.ts rename to test/meta/ForwardedMetaTransactionContext.ts From 6da28f69639a478bf5fb2394e1d05683a02ec4a8 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 10:41:58 -0600 Subject: [PATCH 28/33] fix test suite title --- test/meta/ForwardedMetaTransactionContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/meta/ForwardedMetaTransactionContext.ts b/test/meta/ForwardedMetaTransactionContext.ts index 67e2ac88f..325ef24d6 100644 --- a/test/meta/ForwardedMetaTransactionContext.ts +++ b/test/meta/ForwardedMetaTransactionContext.ts @@ -6,7 +6,7 @@ import { import { expect } from 'chai'; import { ethers } from 'hardhat'; -describe('ForwardedMe$ForwardedMetaTransactionContext', () => { +describe('ForwardedMetaTransactionContext', () => { let instance: $ForwardedMetaTransactionContext; let deployer: SignerWithAddress; From 8797018208537128d211fab2aa46dcd48c7445f0 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 11:50:10 -0600 Subject: [PATCH 29/33] add callMetaTransaction test helper --- test/meta/ForwardedMetaTransactionContext.ts | 89 +++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/test/meta/ForwardedMetaTransactionContext.ts b/test/meta/ForwardedMetaTransactionContext.ts index 325ef24d6..ca7794e6c 100644 --- a/test/meta/ForwardedMetaTransactionContext.ts +++ b/test/meta/ForwardedMetaTransactionContext.ts @@ -3,9 +3,28 @@ import { $ForwardedMetaTransactionContext, $ForwardedMetaTransactionContext__factory, } from '@solidstate/typechain-types'; +import { TypedContractMethod } from '@solidstate/typechain-types/common'; import { expect } from 'chai'; +import { BytesLike } from 'ethers'; import { ethers } from 'hardhat'; +const callMetaTransaction = async ( + signer: SignerWithAddress, + fn: TypedContractMethod<[], [string], 'view'>, + data: BytesLike, + args: any[] = [], +) => { + const tx = await fn.populateTransaction(); + tx.data = ethers.concat([tx.data, data]); + + const result = await signer.call(tx); + + return new ethers.Interface([fn.fragment]).decodeFunctionResult( + fn.name, + result, + ); +}; + describe('ForwardedMetaTransactionContext', () => { let instance: $ForwardedMetaTransactionContext; let deployer: SignerWithAddress; @@ -21,17 +40,14 @@ describe('ForwardedMetaTransactionContext', () => { describe('__internal', () => { describe('#_msgSender()', () => { - it('returns message sender is sender is not trusted forwarder', async () => { - const tx = await instance.$_msgSender.populateTransaction(); - tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); - - const result = await deployer.call(tx); - const decoded = instance.interface.decodeFunctionResult( - '$_msgSender', - result, - ); - - expect(decoded).to.deep.equal([await deployer.getAddress()]); + it('returns message sender if sender is not trusted forwarder', async () => { + expect( + await callMetaTransaction( + deployer, + instance.$_msgSender, + ethers.randomBytes(20), + ), + ).to.deep.equal([await deployer.getAddress()]); }); it('returns forwarded sender if sender is trusted forwarder', async () => { @@ -44,33 +60,26 @@ describe('ForwardedMetaTransactionContext', () => { const forwardedAddress = ethers.hexlify(ethers.randomBytes(20)); - const tx = await instance.$_msgSender.populateTransaction(); - tx.data = ethers.concat([tx.data, forwardedAddress]); - - const result = await trustedForwarder.call(tx); - const decoded = instance.interface.decodeFunctionResult( - '$_msgSender', - result, - ); - - expect(decoded).to.deep.equal([forwardedAddress]); + expect( + await callMetaTransaction( + trustedForwarder, + instance.$_msgSender, + forwardedAddress, + ), + ).to.deep.equal([forwardedAddress]); }); }); describe('#_msgData()', () => { it('returns complete message data if sender is not trusted forwarder', async () => { - const tx = await instance.$_msgData.populateTransaction(); - tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); + const nonSuffixedData = instance.$_msgData.fragment.selector; + const data = ethers.randomBytes(20); // message data is returned as received, demonstrating the malleability of msg.data - const result = await deployer.call(tx); - const decoded = instance.interface.decodeFunctionResult( - '$_msgData', - result, - ); - - expect(decoded).to.deep.equal([tx.data]); + expect( + await callMetaTransaction(deployer, instance.$_msgData, data), + ).to.deep.equal([ethers.concat([nonSuffixedData, data])]); }); it('returns message data without suffix if sender is trusted forwarder', async () => { @@ -81,19 +90,15 @@ describe('ForwardedMetaTransactionContext', () => { await trustedForwarder.getAddress(), ); - const tx = await instance.$_msgData.populateTransaction(); - const nonSuffixedData = tx.data; - tx.data = ethers.concat([tx.data, ethers.randomBytes(20)]); - - // message data is returned as received, demonstrating the malleability of msg.data - - const result = await trustedForwarder.call(tx); - const decoded = instance.interface.decodeFunctionResult( - '$_msgData', - result, - ); + const nonSuffixedData = instance.$_msgData.fragment.selector; - expect(decoded).to.deep.equal([nonSuffixedData]); + expect( + await callMetaTransaction( + trustedForwarder, + instance.$_msgData, + ethers.randomBytes(20), + ), + ).to.deep.equal([nonSuffixedData]); }); }); From 9dc06730c8057d4f4adf0c1a3fec9b86a2acd902 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 12:08:27 -0600 Subject: [PATCH 30/33] enable arguments for callMetaTransaction helper --- test/meta/ForwardedMetaTransactionContext.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/meta/ForwardedMetaTransactionContext.ts b/test/meta/ForwardedMetaTransactionContext.ts index ca7794e6c..a69c12313 100644 --- a/test/meta/ForwardedMetaTransactionContext.ts +++ b/test/meta/ForwardedMetaTransactionContext.ts @@ -5,16 +5,16 @@ import { } from '@solidstate/typechain-types'; import { TypedContractMethod } from '@solidstate/typechain-types/common'; import { expect } from 'chai'; -import { BytesLike } from 'ethers'; +import { BytesLike, ContractMethodArgs } from 'ethers'; import { ethers } from 'hardhat'; const callMetaTransaction = async ( signer: SignerWithAddress, fn: TypedContractMethod<[], [string], 'view'>, data: BytesLike, - args: any[] = [], + args: ContractMethodArgs<[]> = [], ) => { - const tx = await fn.populateTransaction(); + const tx = await fn.populateTransaction(...args); tx.data = ethers.concat([tx.data, data]); const result = await signer.call(tx); From 1750e80ec94dab52cfc80dac190b14687deae908 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 13:31:11 -0600 Subject: [PATCH 31/33] test ForwardedMetaTransactionContext case where suffix is added but is too short --- test/meta/ForwardedMetaTransactionContext.ts | 67 +++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/test/meta/ForwardedMetaTransactionContext.ts b/test/meta/ForwardedMetaTransactionContext.ts index a69c12313..f457e9463 100644 --- a/test/meta/ForwardedMetaTransactionContext.ts +++ b/test/meta/ForwardedMetaTransactionContext.ts @@ -10,7 +10,7 @@ import { ethers } from 'hardhat'; const callMetaTransaction = async ( signer: SignerWithAddress, - fn: TypedContractMethod<[], [string], 'view'>, + fn: TypedContractMethod<[], [string], 'nonpayable' | 'payable' | 'view'>, data: BytesLike, args: ContractMethodArgs<[]> = [], ) => { @@ -40,6 +40,25 @@ describe('ForwardedMetaTransactionContext', () => { describe('__internal', () => { describe('#_msgSender()', () => { + it('returns forwarded sender if sender is trusted forwarder', async () => { + const trustedForwarder = await ethers.getImpersonatedSigner( + await instance.getAddress(), + ); + await instance.$_addTrustedForwarder( + await trustedForwarder.getAddress(), + ); + + const forwardedAddress = ethers.hexlify(ethers.randomBytes(20)); + + expect( + await callMetaTransaction( + trustedForwarder, + instance.$_msgSender, + forwardedAddress, + ), + ).to.deep.equal([forwardedAddress]); + }); + it('returns message sender if sender is not trusted forwarder', async () => { expect( await callMetaTransaction( @@ -50,7 +69,7 @@ describe('ForwardedMetaTransactionContext', () => { ).to.deep.equal([await deployer.getAddress()]); }); - it('returns forwarded sender if sender is trusted forwarder', async () => { + it('returns message sender if message data length is less than suffix length', async () => { const trustedForwarder = await ethers.getImpersonatedSigner( await instance.getAddress(), ); @@ -58,19 +77,43 @@ describe('ForwardedMetaTransactionContext', () => { await trustedForwarder.getAddress(), ); - const forwardedAddress = ethers.hexlify(ethers.randomBytes(20)); + // account for 4-byte selector when calculting suffix length + const suffix = ethers.randomBytes( + Number( + (await instance.$_calldataSuffixLength.staticCall()) - 4n - 1n, + ), + ); expect( await callMetaTransaction( trustedForwarder, instance.$_msgSender, - forwardedAddress, + suffix, ), - ).to.deep.equal([forwardedAddress]); + ).to.deep.equal([await trustedForwarder.getAddress()]); }); }); describe('#_msgData()', () => { + it('returns message data without suffix if sender is trusted forwarder', async () => { + const trustedForwarder = await ethers.getImpersonatedSigner( + await instance.getAddress(), + ); + await instance.$_addTrustedForwarder( + await trustedForwarder.getAddress(), + ); + + const nonSuffixedData = instance.$_msgData.fragment.selector; + + expect( + await callMetaTransaction( + trustedForwarder, + instance.$_msgData, + ethers.randomBytes(20), + ), + ).to.deep.equal([nonSuffixedData]); + }); + it('returns complete message data if sender is not trusted forwarder', async () => { const nonSuffixedData = instance.$_msgData.fragment.selector; const data = ethers.randomBytes(20); @@ -82,7 +125,7 @@ describe('ForwardedMetaTransactionContext', () => { ).to.deep.equal([ethers.concat([nonSuffixedData, data])]); }); - it('returns message data without suffix if sender is trusted forwarder', async () => { + it('returns complete message data if message data length is less than suffix length', async () => { const trustedForwarder = await ethers.getImpersonatedSigner( await instance.getAddress(), ); @@ -91,14 +134,22 @@ describe('ForwardedMetaTransactionContext', () => { ); const nonSuffixedData = instance.$_msgData.fragment.selector; + // account for 4-byte selector when calculting suffix length + const suffix = ethers.randomBytes( + Number( + (await instance.$_calldataSuffixLength.staticCall()) - 4n - 1n, + ), + ); + + // message data is returned as received, demonstrating the malleability of msg.data expect( await callMetaTransaction( trustedForwarder, instance.$_msgData, - ethers.randomBytes(20), + suffix, ), - ).to.deep.equal([nonSuffixedData]); + ).to.deep.equal([ethers.concat([nonSuffixedData, suffix])]); }); }); From 059c414a42fc05d165ae96f1ecd1f569a158a470 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Sat, 22 Mar 2025 11:48:19 -0600 Subject: [PATCH 32/33] allow EOA trusted forwarders --- .../meta/_ForwardedMetaTransactionContext.sol | 14 +--- test/meta/ForwardedMetaTransactionContext.ts | 78 ++++++------------- 2 files changed, 27 insertions(+), 65 deletions(-) diff --git a/contracts/meta/_ForwardedMetaTransactionContext.sol b/contracts/meta/_ForwardedMetaTransactionContext.sol index 2b645f386..ce5c0724a 100644 --- a/contracts/meta/_ForwardedMetaTransactionContext.sol +++ b/contracts/meta/_ForwardedMetaTransactionContext.sol @@ -13,8 +13,6 @@ abstract contract _ForwardedMetaTransactionContext is { using AddressUtils for address; - error ForwardedMetaTransactionContext__TrustedForwarderMustBeContract(); - /** * @inheritdoc _Context * @dev sender is read from the calldata context suffix @@ -91,11 +89,9 @@ abstract contract _ForwardedMetaTransactionContext is function _isTrustedForwarder( address account ) internal view virtual returns (bool trustedStatus) { - trustedStatus = - account.isContract() && - ERC2771Storage - .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) - .trustedForwarders[account]; + trustedStatus = ERC2771Storage + .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) + .trustedForwarders[account]; } /** @@ -103,10 +99,6 @@ abstract contract _ForwardedMetaTransactionContext is * @param account account whose trusted forwarder status to grant */ function _addTrustedForwarder(address account) internal virtual { - // exception for address(this) allows a contract to set itself as a trusted forwarder in its constructor - if (!account.isContract() && account != address(this)) - revert ForwardedMetaTransactionContext__TrustedForwarderMustBeContract(); - ERC2771Storage .layout(ERC2771Storage.DEFAULT_STORAGE_SLOT) .trustedForwarders[account] = true; diff --git a/test/meta/ForwardedMetaTransactionContext.ts b/test/meta/ForwardedMetaTransactionContext.ts index f457e9463..234115636 100644 --- a/test/meta/ForwardedMetaTransactionContext.ts +++ b/test/meta/ForwardedMetaTransactionContext.ts @@ -27,13 +27,18 @@ const callMetaTransaction = async ( describe('ForwardedMetaTransactionContext', () => { let instance: $ForwardedMetaTransactionContext; - let deployer: SignerWithAddress; + let trustedForwarder: SignerWithAddress; + let nonTrustedForwarder: SignerWithAddress; beforeEach(async () => { - [deployer] = await ethers.getSigners(); + let deployer; + [deployer, trustedForwarder, nonTrustedForwarder] = + await ethers.getSigners(); instance = await new $ForwardedMetaTransactionContext__factory( deployer, ).deploy(); + + await instance.$_addTrustedForwarder(await trustedForwarder.getAddress()); }); // TODO: spec @@ -41,13 +46,6 @@ describe('ForwardedMetaTransactionContext', () => { describe('__internal', () => { describe('#_msgSender()', () => { it('returns forwarded sender if sender is trusted forwarder', async () => { - const trustedForwarder = await ethers.getImpersonatedSigner( - await instance.getAddress(), - ); - await instance.$_addTrustedForwarder( - await trustedForwarder.getAddress(), - ); - const forwardedAddress = ethers.hexlify(ethers.randomBytes(20)); expect( @@ -62,21 +60,14 @@ describe('ForwardedMetaTransactionContext', () => { it('returns message sender if sender is not trusted forwarder', async () => { expect( await callMetaTransaction( - deployer, + nonTrustedForwarder, instance.$_msgSender, ethers.randomBytes(20), ), - ).to.deep.equal([await deployer.getAddress()]); + ).to.deep.equal([await nonTrustedForwarder.getAddress()]); }); it('returns message sender if message data length is less than suffix length', async () => { - const trustedForwarder = await ethers.getImpersonatedSigner( - await instance.getAddress(), - ); - await instance.$_addTrustedForwarder( - await trustedForwarder.getAddress(), - ); - // account for 4-byte selector when calculting suffix length const suffix = ethers.randomBytes( Number( @@ -96,13 +87,6 @@ describe('ForwardedMetaTransactionContext', () => { describe('#_msgData()', () => { it('returns message data without suffix if sender is trusted forwarder', async () => { - const trustedForwarder = await ethers.getImpersonatedSigner( - await instance.getAddress(), - ); - await instance.$_addTrustedForwarder( - await trustedForwarder.getAddress(), - ); - const nonSuffixedData = instance.$_msgData.fragment.selector; expect( @@ -121,18 +105,15 @@ describe('ForwardedMetaTransactionContext', () => { // message data is returned as received, demonstrating the malleability of msg.data expect( - await callMetaTransaction(deployer, instance.$_msgData, data), + await callMetaTransaction( + nonTrustedForwarder, + instance.$_msgData, + data, + ), ).to.deep.equal([ethers.concat([nonSuffixedData, data])]); }); it('returns complete message data if message data length is less than suffix length', async () => { - const trustedForwarder = await ethers.getImpersonatedSigner( - await instance.getAddress(), - ); - await instance.$_addTrustedForwarder( - await trustedForwarder.getAddress(), - ); - const nonSuffixedData = instance.$_msgData.fragment.selector; // account for 4-byte selector when calculting suffix length const suffix = ethers.randomBytes( @@ -163,15 +144,17 @@ describe('ForwardedMetaTransactionContext', () => { describe('#_isTrustedForwarder(address)', () => { it('returns trusted forwarder status of account', async () => { - expect(await instance.$_isTrustedForwarder(await deployer.getAddress())) - .to.be.false; - expect(await instance.$_isTrustedForwarder(await instance.getAddress())) - .to.be.false; - - await instance.$_addTrustedForwarder(await instance.getAddress()); + expect( + await instance.$_isTrustedForwarder( + await nonTrustedForwarder.getAddress(), + ), + ).to.be.false; - expect(await instance.$_isTrustedForwarder(await instance.getAddress())) - .to.be.true; + expect( + await instance.$_isTrustedForwarder( + await trustedForwarder.getAddress(), + ), + ).to.be.true; }); }); @@ -182,19 +165,6 @@ describe('ForwardedMetaTransactionContext', () => { expect(await instance.$_isTrustedForwarder(await instance.getAddress())) .to.be.true; }); - - describe('reverts if', () => { - it('account is not a contract', async () => { - // this is enforced via a code check - // there's an exception for address(this), but this is difficult to test here - await expect( - instance.$_addTrustedForwarder(ethers.ZeroAddress), - ).to.be.revertedWithCustomError( - instance, - 'ForwardedMetaTransactionContext__TrustedForwarderMustBeContract', - ); - }); - }); }); describe('#_removeTrustedForwarder(address)', () => { From b87a95d4936748b9ca74e3d82ea6a57100c5310b Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Sat, 22 Mar 2025 11:53:40 -0600 Subject: [PATCH 33/33] remove dev note --- contracts/meta/_ForwardedMetaTransactionContext.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/meta/_ForwardedMetaTransactionContext.sol b/contracts/meta/_ForwardedMetaTransactionContext.sol index ce5c0724a..21fe4fa02 100644 --- a/contracts/meta/_ForwardedMetaTransactionContext.sol +++ b/contracts/meta/_ForwardedMetaTransactionContext.sol @@ -82,7 +82,6 @@ abstract contract _ForwardedMetaTransactionContext is /** * @notice query whether account is a trusted ERC2771 forwarder - * @dev only contracts can be considered trusted forwarders * @param account address to query * @return trustedStatus whether account is a trusted forwarder */