From c9c3e7007054bbaf3c8d6d088e98d816aa39ace4 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 00:24:34 -0600 Subject: [PATCH 01/28] remove view restriction from context functions --- contracts/meta/_Context.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/meta/_Context.sol b/contracts/meta/_Context.sol index 303eab6c..3b2f6669 100644 --- a/contracts/meta/_Context.sol +++ b/contracts/meta/_Context.sol @@ -13,7 +13,7 @@ abstract contract _Context is _IContext { * @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) { + function _msgSender() internal virtual returns (address msgSender) { msgSender = msg.sender; } @@ -22,7 +22,7 @@ abstract contract _Context is _IContext { * @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) { + function _msgData() internal virtual returns (bytes calldata msgData) { msgData = msg.data; } From d36ebea3f61e4b06a67bfe020b006bde7a176fdd Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 12:18:48 -0600 Subject: [PATCH 02/28] remove view restrictions from functions that call context functions --- contracts/access/access_control/_AccessControl.sol | 2 +- contracts/meta/ForwardedMetaTransactionContext.sol | 2 -- contracts/meta/_ForwardedMetaTransactionContext.sol | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/contracts/access/access_control/_AccessControl.sol b/contracts/access/access_control/_AccessControl.sol index eb891a4c..ecbcc45f 100644 --- a/contracts/access/access_control/_AccessControl.sol +++ b/contracts/access/access_control/_AccessControl.sol @@ -45,7 +45,7 @@ abstract contract _AccessControl is _IAccessControl, _Context { * @notice revert if sender does not have given role * @param role role to query */ - function _checkRole(bytes32 role) internal view virtual { + function _checkRole(bytes32 role) internal virtual { _checkRole(role, _msgSender()); } diff --git a/contracts/meta/ForwardedMetaTransactionContext.sol b/contracts/meta/ForwardedMetaTransactionContext.sol index 7dc537ad..4a2a09d4 100644 --- a/contracts/meta/ForwardedMetaTransactionContext.sol +++ b/contracts/meta/ForwardedMetaTransactionContext.sol @@ -27,7 +27,6 @@ abstract contract ForwardedMetaTransactionContext is */ function _msgSender() internal - view virtual override(_Context, _ForwardedMetaTransactionContext) returns (address msgSender) @@ -40,7 +39,6 @@ abstract contract ForwardedMetaTransactionContext is */ function _msgData() internal - view virtual override(_Context, _ForwardedMetaTransactionContext) returns (bytes calldata msgData) diff --git a/contracts/meta/_ForwardedMetaTransactionContext.sol b/contracts/meta/_ForwardedMetaTransactionContext.sol index 2b645f38..2fc8ec15 100644 --- a/contracts/meta/_ForwardedMetaTransactionContext.sol +++ b/contracts/meta/_ForwardedMetaTransactionContext.sol @@ -21,7 +21,6 @@ abstract contract _ForwardedMetaTransactionContext is */ function _msgSender() internal - view virtual override returns (address msgSender) @@ -48,7 +47,6 @@ abstract contract _ForwardedMetaTransactionContext is */ function _msgData() internal - view virtual override returns (bytes calldata msgData) From 26137acd39e2270e216b539826b8e301549d3ad0 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 13:00:04 -0600 Subject: [PATCH 03/28] set evmVerion to cancun --- hardhat.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/hardhat.config.ts b/hardhat.config.ts index 81a0fdcc..3301da64 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -18,6 +18,7 @@ const config: HardhatUserConfig = { solidity: { version: '0.8.28', settings: { + evmVersion: 'cancun', optimizer: { enabled: true, runs: 200, From a7510d4fce83a6012cb34ba001f519471937139a Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 13:15:20 -0600 Subject: [PATCH 04/28] add draft ECDSAMetaTransactionContext contracts --- .../meta/ECDSAMetaTransactionContext.sol | 59 +++++++ .../meta/IECDSAMetaTransactionContext.sol | 13 ++ .../meta/_ECDSAMetaTransactionContext.sol | 162 ++++++++++++++++++ .../meta/_IECDSAMetaTransactionContext.sol | 8 + 4 files changed, 242 insertions(+) create mode 100644 contracts/meta/ECDSAMetaTransactionContext.sol create mode 100644 contracts/meta/IECDSAMetaTransactionContext.sol create mode 100644 contracts/meta/_ECDSAMetaTransactionContext.sol create mode 100644 contracts/meta/_IECDSAMetaTransactionContext.sol diff --git a/contracts/meta/ECDSAMetaTransactionContext.sol b/contracts/meta/ECDSAMetaTransactionContext.sol new file mode 100644 index 00000000..53ea08af --- /dev/null +++ b/contracts/meta/ECDSAMetaTransactionContext.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IERC5267 } from '../interfaces/IERC5267.sol'; +import { Context } from './Context.sol'; +import { _Context } from './_Context.sol'; +import { IECDSAMetaTransactionContext } from './IECDSAMetaTransactionContext.sol'; +import { _ECDSAMetaTransactionContext } from './_ECDSAMetaTransactionContext.sol'; + +abstract contract ECDSAMetaTransactionContext is + IECDSAMetaTransactionContext, + _ECDSAMetaTransactionContext, + Context +{ + /** + * @inheritdoc IERC5267 + */ + function eip712Domain() + external + view + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + return _eip712Domain(); + } + + function _msgSender() + internal + override(_Context, _ECDSAMetaTransactionContext) + returns (address msgSender) + { + msgSender = super._msgSender(); + } + + function _msgData() + internal + override(_Context, _ECDSAMetaTransactionContext) + returns (bytes calldata msgData) + { + msgData = super._msgData(); + } + + function _calldataSuffixLength() + internal + view + override(_Context, _ECDSAMetaTransactionContext) + returns (uint256 length) + { + length = super._calldataSuffixLength(); + } +} diff --git a/contracts/meta/IECDSAMetaTransactionContext.sol b/contracts/meta/IECDSAMetaTransactionContext.sol new file mode 100644 index 00000000..97a769aa --- /dev/null +++ b/contracts/meta/IECDSAMetaTransactionContext.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IERC5267 } from '../interfaces/IERC5267.sol'; +import { IContext } from './IContext.sol'; +import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol'; + +interface IECDSAMetaTransactionContext is + _IECDSAMetaTransactionContext, + IContext, + IERC5267 +{} diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol new file mode 100644 index 00000000..ea512849 --- /dev/null +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { ECDSA } from '../cryptography/ECDSA.sol'; +import { _Context } from './_Context.sol'; +import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol'; + +abstract contract _ECDSAMetaTransactionContext is + _IECDSAMetaTransactionContext, + _Context +{ + using ECDSA for bytes32; + + bytes32 internal constant EIP_712_TYPE_HASH = + keccak256('ECDSAMetaTransaction(bytes msgData,uint256 nonce)'); + + function _eip712Domain() + internal + view + virtual + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + return ( + hex'0f', // 01100 + '', + '', + block.chainid, + address(this), + bytes32(0), + new uint256[](0) + ); + } + + /** + * @inheritdoc _Context + * @dev sender is read from the calldata context suffix + */ + function _msgSender() + internal + virtual + override + returns (address msgSender) + { + uint256 dataLength = msg.data.length; + uint256 suffixLength = _calldataSuffixLength(); + + if (dataLength >= suffixLength) { + // calldata is long enough that it might have a suffix + // check transient storage to see if sender has been derived already + + assembly { + msgSender := tload(9000) + } + + if (msgSender == address(0)) { + // no sender found in transient storage, so attempt to derive it from signature + + unchecked { + (msgSender, ) = _processCalldata(dataLength - suffixLength); + } + } + } else { + msgSender = super._msgSender(); + } + } + + /** + * @inheritdoc _Context + */ + function _msgData() + internal + virtual + override + returns (bytes calldata msgData) + { + uint256 dataLength = msg.data.length; + uint256 suffixLength = _calldataSuffixLength(); + + if (dataLength >= suffixLength) { + // calldata is long enough that it might have a suffix + // check transient storage to see if msgData split index has been derived already + + uint256 split; + + assembly { + split := tload(9001) + } + + if (split == 0) { + // no msgData split index found in transient storage, so attempt to derive it from signature + + unchecked { + (, split) = _processCalldata(dataLength - suffixLength); + } + } + + msgData = msg.data[:split]; + } else { + msgData = super._msgData(); + } + } + + /** + * @inheritdoc _Context + * @dev this Context extension defines an address suffix with a length of 20 + */ + function _calldataSuffixLength() + internal + view + virtual + override + returns (uint256 length) + { + length = 97; + } + + function _processCalldata( + uint256 split + ) private returns (address msgSender, uint256 msgDataIndex) { + unchecked { + bytes calldata msgData = msg.data[:split]; + uint256 nonce = uint256(bytes32(msg.data[split:split + 32])); + + // TODO: include msg.sender in hash to restrict forwarder + bytes32 hash = keccak256( + abi.encode(EIP_712_TYPE_HASH, keccak256(msgData), nonce) + ); + + bytes calldata signature = msg.data[split + 32:]; + + // TODO: see what happens if split calldata v r s + address signer = hash.recover(signature); + + // TODO: invalidate nonce + + if (signer == address(0)) { + msgSender = super._msgSender(); + msgDataIndex = super._msgData().length; + } else { + msgSender = signer; + msgDataIndex = split; + } + } + + assembly { + // TODO: suppress warning + // TODO: standardize location + // TODO: pack (msgDataIndex as bytes12) + tstore(9000, msgSender) + tstore(9001, msgDataIndex) + } + } +} diff --git a/contracts/meta/_IECDSAMetaTransactionContext.sol b/contracts/meta/_IECDSAMetaTransactionContext.sol new file mode 100644 index 00000000..a9abf4ea --- /dev/null +++ b/contracts/meta/_IECDSAMetaTransactionContext.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { _IERC5267 } from '../interfaces/_IERC5267.sol'; +import { _IContext } from './_IContext.sol'; + +interface _IECDSAMetaTransactionContext is _IContext, _IERC5267 {} From b185260d3cfd5921b1f1638875e86a17aff2bc8d Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 13:45:08 -0600 Subject: [PATCH 05/28] use non-reverting recover for ECDSA context --- contracts/meta/_ECDSAMetaTransactionContext.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index ea512849..e2b2f7d2 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -138,7 +138,7 @@ abstract contract _ECDSAMetaTransactionContext is bytes calldata signature = msg.data[split + 32:]; // TODO: see what happens if split calldata v r s - address signer = hash.recover(signature); + address signer = hash.tryRecover(signature); // TODO: invalidate nonce From 4d633a091024d8e3dab9cc05127c03baee95a403 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 14:28:05 -0600 Subject: [PATCH 06/28] fix _eip712Domain output --- contracts/meta/_ECDSAMetaTransactionContext.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index e2b2f7d2..70daba16 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -30,7 +30,7 @@ abstract contract _ECDSAMetaTransactionContext is ) { return ( - hex'0f', // 01100 + hex'0c', // 01100 '', '', block.chainid, From 5bef448f69379ff492e3b3c3655f2e43f3b63fde Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 15:02:15 -0600 Subject: [PATCH 07/28] reference ERC5267 fields constant in ECDSAMetaTransactionContext --- contracts/meta/_ECDSAMetaTransactionContext.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 70daba16..4f4f1dd2 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; import { ECDSA } from '../cryptography/ECDSA.sol'; +import { EIP712 } from '../cryptography/EIP712.sol'; import { _Context } from './_Context.sol'; import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol'; @@ -30,7 +31,7 @@ abstract contract _ECDSAMetaTransactionContext is ) { return ( - hex'0c', // 01100 + EIP712.ERC5267_FIELDS_01100, '', '', block.chainid, From 32e632dea70cb44c78196ebbe701acc6cb45107d Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 15:07:25 -0600 Subject: [PATCH 08/28] fix hash struct procedure --- .../meta/_ECDSAMetaTransactionContext.sol | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 4f4f1dd2..00e989f7 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -132,14 +132,45 @@ abstract contract _ECDSAMetaTransactionContext is uint256 nonce = uint256(bytes32(msg.data[split:split + 32])); // TODO: include msg.sender in hash to restrict forwarder - bytes32 hash = keccak256( + // TODO: include msg.value + bytes32 structHash = keccak256( abi.encode(EIP_712_TYPE_HASH, keccak256(msgData), nonce) ); + bytes32 domainSeparator = EIP712.calculateDomainSeparator_01100(); + + bytes32 signedHash; + + assembly { + // assembly block equivalent to: + // + // signedHash = keccak256( + // abi.encodePacked( + // uint16(0x1901), + // domainSeparator, + // structHash + // ) + // ); + + // load free memory pointer + let pointer := mload(64) + + // this magic value is the EIP-191 signed data header, consisting of + // the hardcoded 0x19 and the one-byte version 0x01 + mstore( + pointer, + 0x1901000000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(pointer, 2), domainSeparator) + mstore(add(pointer, 34), structHash) + + signedHash := keccak256(pointer, 66) + } + bytes calldata signature = msg.data[split + 32:]; // TODO: see what happens if split calldata v r s - address signer = hash.tryRecover(signature); + address signer = signedHash.tryRecover(signature); // TODO: invalidate nonce From 86662f601c981d594a185996adf5aad0dfb70662 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 15:12:42 -0600 Subject: [PATCH 09/28] test ECDSAMetaTransactionContext --- lib/erc20_permit.ts | 32 ++++- test/meta/ECDSAMetaTransactionContext.ts | 155 +++++++++++++++++++++++ 2 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 test/meta/ECDSAMetaTransactionContext.ts diff --git a/lib/erc20_permit.ts b/lib/erc20_permit.ts index 08c6ac6f..a5dda44a 100644 --- a/lib/erc20_permit.ts +++ b/lib/erc20_permit.ts @@ -1,6 +1,6 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { IERC2612, IERC5267 } from '@solidstate/typechain-types'; -import { Signature } from 'ethers'; +import { BytesLike, Signature } from 'ethers'; import { ethers } from 'hardhat'; interface Domain { @@ -99,9 +99,33 @@ const signERC2612Permit = async ( const signature = await owner.signTypedData(domain, types, values); - const permit = ethers.Signature.from(signature); + return ethers.Signature.from(signature); +}; + +// TODO: rename file or move to new file +const signECDSAMetaTransaction = async ( + instance: IERC5267, + signer: SignerWithAddress, + msgData: BytesLike, + nonce: bigint, +): Promise => { + const domain = await getDomain(instance); + + const types = { + ECDSAMetaTransaction: [ + { name: 'msgData', type: 'bytes' }, + { name: 'nonce', type: 'uint256' }, + ], + }; + + const values = { + msgData, + nonce, + }; + + const signature = await signer.signTypedData(domain, types, values); - return permit; + return ethers.Signature.from(signature); }; -export { signERC2612Permit }; +export { signERC2612Permit, signECDSAMetaTransaction }; diff --git a/test/meta/ECDSAMetaTransactionContext.ts b/test/meta/ECDSAMetaTransactionContext.ts new file mode 100644 index 00000000..931d7289 --- /dev/null +++ b/test/meta/ECDSAMetaTransactionContext.ts @@ -0,0 +1,155 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { signECDSAMetaTransaction } from '@solidstate/library'; +import { + $ECDSAMetaTransactionContext, + $ECDSAMetaTransactionContext__factory, +} from '@solidstate/typechain-types'; +import { TypedContractMethod } from '@solidstate/typechain-types/common'; +import { expect } from 'chai'; +import { BytesLike, ContractMethodArgs } from 'ethers'; +import { ethers } from 'hardhat'; + +const callMetaTransaction = async ( + signer: SignerWithAddress, + fn: TypedContractMethod<[], [string], 'nonpayable' | 'payable' | 'view'>, + data: BytesLike, + args: ContractMethodArgs<[]> = [], +) => { + const tx = await fn.populateTransaction(...args); + tx.data = ethers.concat([tx.data, data]); + + const result = await signer.call(tx); + + return new ethers.Interface([fn.fragment]).decodeFunctionResult( + fn.name, + result, + ); +}; + +describe('ECDSAMetaTransactionContext', () => { + let instance: $ECDSAMetaTransactionContext; + let deployer: SignerWithAddress; + let forwarder: SignerWithAddress; + let signer: SignerWithAddress; + + beforeEach(async () => { + [deployer, forwarder, signer] = await ethers.getSigners(); + instance = await new $ECDSAMetaTransactionContext__factory( + deployer, + ).deploy(); + }); + + // TODO: spec + + // TODO: test multiple calls in same tx to validate that transient storage is used correctly + + describe('__internal', () => { + describe('#_msgSender()', () => { + it('returns signer if signature is valid', async () => { + const data = instance.$_msgSender.fragment.selector; + const nonce = 1n; + + const signature = await signECDSAMetaTransaction( + instance, + signer, + data, + nonce, + ); + + const suffix = ethers.concat([ + ethers.toBeHex(nonce, 32), + signature.serialized, + ]); + + expect( + await callMetaTransaction(forwarder, instance.$_msgSender, suffix), + ).to.deep.equal([await signer.getAddress()]); + }); + + it('returns message sender if signature is invalid', async () => { + const suffix = ethers.randomBytes( + Number(await instance.$_calldataSuffixLength.staticCall()), + ); + + expect( + await callMetaTransaction(forwarder, instance.$_msgSender, suffix), + ).to.deep.equal([await forwarder.getAddress()]); + }); + + it('returns message sender if message data length is less than suffix length', async () => { + it('returns message sender if message data length is less than suffix length', async () => { + // account for 4-byte selector when calculting suffix length + const suffix = ethers.randomBytes( + Number( + (await instance.$_calldataSuffixLength.staticCall()) - 4n - 1n, + ), + ); + + expect( + await callMetaTransaction(forwarder, instance.$_msgSender, suffix), + ).to.deep.equal([await forwarder.getAddress()]); + }); + }); + }); + + describe('#_msgData()', () => { + it('returns message data without suffix if signature is valid', async () => { + const nonSuffixedData = instance.$_msgData.fragment.selector; + const nonce = 1n; + + const signature = await signECDSAMetaTransaction( + instance, + signer, + nonSuffixedData, + nonce, + ); + + const suffix = ethers.concat([ + ethers.toBeHex(nonce, 32), + signature.serialized, + ]); + + expect( + await callMetaTransaction(forwarder, instance.$_msgData, suffix), + ).to.deep.equal([nonSuffixedData]); + }); + + it('returns complete message data if signature is invalid', async () => { + const nonSuffixedData = instance.$_msgData.fragment.selector; + const suffix = ethers.randomBytes( + Number(await instance.$_calldataSuffixLength.staticCall()), + ); + + // message data is returned as received, demonstrating the malleability of msg.data + + expect( + await callMetaTransaction(forwarder, instance.$_msgData, suffix), + ).to.deep.equal([ethers.concat([nonSuffixedData, suffix])]); + }); + + it('returns complete message data if message data length is less than suffix length', async () => { + 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(forwarder, instance.$_msgData, suffix), + ).to.deep.equal([ethers.concat([nonSuffixedData, suffix])]); + }); + }); + + describe('#_calldataSuffixLength()', () => { + it('returns 97', async () => { + expect(await instance.$_calldataSuffixLength.staticCall()).to.equal( + 97n, + ); + }); + }); + }); +}); From be593430a2f76741f5e37daaf5bf3af8ac299329 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 15:17:36 -0600 Subject: [PATCH 10/28] fix test nesting --- test/meta/ECDSAMetaTransactionContext.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/meta/ECDSAMetaTransactionContext.ts b/test/meta/ECDSAMetaTransactionContext.ts index 931d7289..0a3c3df5 100644 --- a/test/meta/ECDSAMetaTransactionContext.ts +++ b/test/meta/ECDSAMetaTransactionContext.ts @@ -77,18 +77,16 @@ describe('ECDSAMetaTransactionContext', () => { }); it('returns message sender if message data length is less than suffix length', async () => { - it('returns message sender if message data length is less than suffix length', async () => { - // account for 4-byte selector when calculting suffix length - const suffix = ethers.randomBytes( - Number( - (await instance.$_calldataSuffixLength.staticCall()) - 4n - 1n, - ), - ); - - expect( - await callMetaTransaction(forwarder, instance.$_msgSender, suffix), - ).to.deep.equal([await forwarder.getAddress()]); - }); + // account for 4-byte selector when calculting suffix length + const suffix = ethers.randomBytes( + Number( + (await instance.$_calldataSuffixLength.staticCall()) - 4n - 1n, + ), + ); + + expect( + await callMetaTransaction(forwarder, instance.$_msgSender, suffix), + ).to.deep.equal([await forwarder.getAddress()]); }); }); From 054d056b5f1068d0a451dada28a9316cccc6ce5f Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 15:53:01 -0600 Subject: [PATCH 11/28] use semi-standardized transient storage slot --- .../meta/_ECDSAMetaTransactionContext.sol | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 00e989f7..8e5930a6 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -16,6 +16,11 @@ abstract contract _ECDSAMetaTransactionContext is bytes32 internal constant EIP_712_TYPE_HASH = keccak256('ECDSAMetaTransaction(bytes msgData,uint256 nonce)'); + bytes32 + internal constant ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT = + keccak256(abi.encode(uint256(EIP_712_TYPE_HASH) - 1)) & + ~bytes32(uint256(0xff)); + function _eip712Domain() internal view @@ -58,8 +63,10 @@ abstract contract _ECDSAMetaTransactionContext is // calldata is long enough that it might have a suffix // check transient storage to see if sender has been derived already + bytes32 slot = ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT; + assembly { - msgSender := tload(9000) + msgSender := tload(slot) } if (msgSender == address(0)) { @@ -92,8 +99,10 @@ abstract contract _ECDSAMetaTransactionContext is uint256 split; + bytes32 slot = ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT; + assembly { - split := tload(9001) + split := tload(add(slot, 1)) } if (split == 0) { @@ -183,12 +192,13 @@ abstract contract _ECDSAMetaTransactionContext is } } + bytes32 slot = ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT; + assembly { // TODO: suppress warning - // TODO: standardize location // TODO: pack (msgDataIndex as bytes12) - tstore(9000, msgSender) - tstore(9001, msgDataIndex) + tstore(slot, msgSender) + tstore(add(slot, 1), msgDataIndex) } } } From 6087a18a60196cd1cfb0135f2e39b325d5b388f6 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 16:17:29 -0600 Subject: [PATCH 12/28] support msg.value in ECDSAMetaTransactionContext --- contracts/meta/_ECDSAMetaTransactionContext.sol | 17 +++++++++++------ lib/erc20_permit.ts | 3 +++ test/meta/ECDSAMetaTransactionContext.ts | 6 ++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 8e5930a6..f04ac83a 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -14,7 +14,9 @@ abstract contract _ECDSAMetaTransactionContext is using ECDSA for bytes32; bytes32 internal constant EIP_712_TYPE_HASH = - keccak256('ECDSAMetaTransaction(bytes msgData,uint256 nonce)'); + keccak256( + 'ECDSAMetaTransaction(bytes msgData,uint256 msgValue,uint256 nonce)' + ); bytes32 internal constant ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT = @@ -139,11 +141,16 @@ abstract contract _ECDSAMetaTransactionContext is unchecked { bytes calldata msgData = msg.data[:split]; uint256 nonce = uint256(bytes32(msg.data[split:split + 32])); + bytes calldata signature = msg.data[split + 32:]; - // TODO: include msg.sender in hash to restrict forwarder - // TODO: include msg.value + // TODO: include msg.sender in hash to restrict forwarder? bytes32 structHash = keccak256( - abi.encode(EIP_712_TYPE_HASH, keccak256(msgData), nonce) + abi.encode( + EIP_712_TYPE_HASH, + keccak256(msgData), + msg.value, + nonce + ) ); bytes32 domainSeparator = EIP712.calculateDomainSeparator_01100(); @@ -176,8 +183,6 @@ abstract contract _ECDSAMetaTransactionContext is signedHash := keccak256(pointer, 66) } - bytes calldata signature = msg.data[split + 32:]; - // TODO: see what happens if split calldata v r s address signer = signedHash.tryRecover(signature); diff --git a/lib/erc20_permit.ts b/lib/erc20_permit.ts index a5dda44a..d18defef 100644 --- a/lib/erc20_permit.ts +++ b/lib/erc20_permit.ts @@ -107,6 +107,7 @@ const signECDSAMetaTransaction = async ( instance: IERC5267, signer: SignerWithAddress, msgData: BytesLike, + msgValue: bigint, nonce: bigint, ): Promise => { const domain = await getDomain(instance); @@ -114,12 +115,14 @@ const signECDSAMetaTransaction = async ( const types = { ECDSAMetaTransaction: [ { name: 'msgData', type: 'bytes' }, + { name: 'msgValue', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, ], }; const values = { msgData, + msgValue, nonce, }; diff --git a/test/meta/ECDSAMetaTransactionContext.ts b/test/meta/ECDSAMetaTransactionContext.ts index 0a3c3df5..aa34877f 100644 --- a/test/meta/ECDSAMetaTransactionContext.ts +++ b/test/meta/ECDSAMetaTransactionContext.ts @@ -43,16 +43,20 @@ describe('ECDSAMetaTransactionContext', () => { // TODO: test multiple calls in same tx to validate that transient storage is used correctly + // TODO: test msg.value revert cases + describe('__internal', () => { describe('#_msgSender()', () => { it('returns signer if signature is valid', async () => { const data = instance.$_msgSender.fragment.selector; const nonce = 1n; + const value = 0n; const signature = await signECDSAMetaTransaction( instance, signer, data, + value, nonce, ); @@ -94,11 +98,13 @@ describe('ECDSAMetaTransactionContext', () => { it('returns message data without suffix if signature is valid', async () => { const nonSuffixedData = instance.$_msgData.fragment.selector; const nonce = 1n; + const value = 0n; const signature = await signECDSAMetaTransaction( instance, signer, nonSuffixedData, + value, nonce, ); From e71e5e9aeb2abc2a504b1107c473cf6708d515e9 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 16:18:18 -0600 Subject: [PATCH 13/28] add failing nonce revert tests --- test/meta/ECDSAMetaTransactionContext.ts | 66 ++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/test/meta/ECDSAMetaTransactionContext.ts b/test/meta/ECDSAMetaTransactionContext.ts index aa34877f..0fee4dca 100644 --- a/test/meta/ECDSAMetaTransactionContext.ts +++ b/test/meta/ECDSAMetaTransactionContext.ts @@ -26,6 +26,18 @@ const callMetaTransaction = async ( ); }; +const sendMetaTransaction = async ( + signer: SignerWithAddress, + fn: TypedContractMethod<[], [string], 'nonpayable' | 'payable' | 'view'>, + data: BytesLike, + args: ContractMethodArgs<[]> = [], +) => { + const tx = await fn.populateTransaction(...args); + tx.data = ethers.concat([tx.data, data]); + + return await signer.sendTransaction(tx); +}; + describe('ECDSAMetaTransactionContext', () => { let instance: $ECDSAMetaTransactionContext; let deployer: SignerWithAddress; @@ -92,6 +104,33 @@ describe('ECDSAMetaTransactionContext', () => { await callMetaTransaction(forwarder, instance.$_msgSender, suffix), ).to.deep.equal([await forwarder.getAddress()]); }); + + describe('reverts if', () => { + it('nonce has been used', async () => { + const data = instance.$_msgSender.fragment.selector; + const nonce = 1n; + const value = 0n; + + const signature = await signECDSAMetaTransaction( + instance, + signer, + data, + value, + nonce, + ); + + const suffix = ethers.concat([ + ethers.toBeHex(nonce, 32), + signature.serialized, + ]); + + await sendMetaTransaction(forwarder, instance.$_msgSender, suffix); + + await expect( + callMetaTransaction(forwarder, instance.$_msgSender, suffix), + ).to.be.revertedWith('TODO'); + }); + }); }); describe('#_msgData()', () => { @@ -146,6 +185,33 @@ describe('ECDSAMetaTransactionContext', () => { await callMetaTransaction(forwarder, instance.$_msgData, suffix), ).to.deep.equal([ethers.concat([nonSuffixedData, suffix])]); }); + + describe('reverts if', () => { + it('nonce has been used', async () => { + const data = instance.$_msgData.fragment.selector; + const nonce = 1n; + const value = 0n; + + const signature = await signECDSAMetaTransaction( + instance, + signer, + data, + value, + nonce, + ); + + const suffix = ethers.concat([ + ethers.toBeHex(nonce, 32), + signature.serialized, + ]); + + await sendMetaTransaction(forwarder, instance.$_msgData, suffix); + + await expect( + callMetaTransaction(forwarder, instance.$_msgData, suffix), + ).to.be.revertedWith('TODO'); + }); + }); }); describe('#_calldataSuffixLength()', () => { From d1169a54841a40b0487b79d60e57a238814cbdeb Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 16:22:29 -0600 Subject: [PATCH 14/28] test incorrect msg.value --- test/meta/ECDSAMetaTransactionContext.ts | 29 ++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/test/meta/ECDSAMetaTransactionContext.ts b/test/meta/ECDSAMetaTransactionContext.ts index 0fee4dca..7df8a0f7 100644 --- a/test/meta/ECDSAMetaTransactionContext.ts +++ b/test/meta/ECDSAMetaTransactionContext.ts @@ -26,6 +26,8 @@ const callMetaTransaction = async ( ); }; +// TODO: support msg.value in helper functions + const sendMetaTransaction = async ( signer: SignerWithAddress, fn: TypedContractMethod<[], [string], 'nonpayable' | 'payable' | 'view'>, @@ -55,8 +57,6 @@ describe('ECDSAMetaTransactionContext', () => { // TODO: test multiple calls in same tx to validate that transient storage is used correctly - // TODO: test msg.value revert cases - describe('__internal', () => { describe('#_msgSender()', () => { it('returns signer if signature is valid', async () => { @@ -105,6 +105,31 @@ describe('ECDSAMetaTransactionContext', () => { ).to.deep.equal([await forwarder.getAddress()]); }); + it('returns incorrect signer if message value is incorrect', async () => { + const data = instance.$_msgSender.fragment.selector; + const nonce = 1n; + const value = 1n; + + const signature = await signECDSAMetaTransaction( + instance, + signer, + data, + value, + nonce, + ); + + const suffix = ethers.concat([ + ethers.toBeHex(nonce, 32), + signature.serialized, + ]); + + // a valid address is returned, but it is not the correct signer + + expect( + await callMetaTransaction(forwarder, instance.$_msgSender, suffix), + ).not.to.deep.equal([await signer.getAddress()]); + }); + describe('reverts if', () => { it('nonce has been used', async () => { const data = instance.$_msgSender.fragment.selector; From c88d41130137e890d252bddca225147931a73379 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 16:40:34 -0600 Subject: [PATCH 15/28] fix comment --- contracts/meta/_ECDSAMetaTransactionContext.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index f04ac83a..a4f25895 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -123,7 +123,7 @@ abstract contract _ECDSAMetaTransactionContext is /** * @inheritdoc _Context - * @dev this Context extension defines an address suffix with a length of 20 + * @dev this Context extension defines an address suffix with a length of 97 */ function _calldataSuffixLength() internal From 5bba739a2d45b167209ab83231a0cd3ecb710537 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 16:47:03 -0600 Subject: [PATCH 16/28] replae nonce in suffix with expected signer address --- .../meta/_ECDSAMetaTransactionContext.sol | 18 ++++++++++-------- test/meta/ECDSAMetaTransactionContext.ts | 14 +++++++------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index a4f25895..54f60e9e 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -123,7 +123,7 @@ abstract contract _ECDSAMetaTransactionContext is /** * @inheritdoc _Context - * @dev this Context extension defines an address suffix with a length of 97 + * @dev this Context extension defines an address suffix with a length of 85 */ function _calldataSuffixLength() internal @@ -132,7 +132,7 @@ abstract contract _ECDSAMetaTransactionContext is override returns (uint256 length) { - length = 97; + length = 85; } function _processCalldata( @@ -140,8 +140,11 @@ abstract contract _ECDSAMetaTransactionContext is ) private returns (address msgSender, uint256 msgDataIndex) { unchecked { bytes calldata msgData = msg.data[:split]; - uint256 nonce = uint256(bytes32(msg.data[split:split + 32])); - bytes calldata signature = msg.data[split + 32:]; + msgSender = address(bytes20(msg.data[split:split + 20])); + bytes calldata signature = msg.data[split + 20:]; + + // TODO: lookup nonce + uint256 nonce = 1; // TODO: include msg.sender in hash to restrict forwarder? bytes32 structHash = keccak256( @@ -188,12 +191,11 @@ abstract contract _ECDSAMetaTransactionContext is // TODO: invalidate nonce - if (signer == address(0)) { + if (signer == msgSender) { + msgDataIndex = split; + } else { msgSender = super._msgSender(); msgDataIndex = super._msgData().length; - } else { - msgSender = signer; - msgDataIndex = split; } } diff --git a/test/meta/ECDSAMetaTransactionContext.ts b/test/meta/ECDSAMetaTransactionContext.ts index 7df8a0f7..9b943691 100644 --- a/test/meta/ECDSAMetaTransactionContext.ts +++ b/test/meta/ECDSAMetaTransactionContext.ts @@ -73,7 +73,7 @@ describe('ECDSAMetaTransactionContext', () => { ); const suffix = ethers.concat([ - ethers.toBeHex(nonce, 32), + await signer.getAddress(), signature.serialized, ]); @@ -119,7 +119,7 @@ describe('ECDSAMetaTransactionContext', () => { ); const suffix = ethers.concat([ - ethers.toBeHex(nonce, 32), + await signer.getAddress(), signature.serialized, ]); @@ -145,7 +145,7 @@ describe('ECDSAMetaTransactionContext', () => { ); const suffix = ethers.concat([ - ethers.toBeHex(nonce, 32), + await signer.getAddress(), signature.serialized, ]); @@ -173,7 +173,7 @@ describe('ECDSAMetaTransactionContext', () => { ); const suffix = ethers.concat([ - ethers.toBeHex(nonce, 32), + await signer.getAddress(), signature.serialized, ]); @@ -226,7 +226,7 @@ describe('ECDSAMetaTransactionContext', () => { ); const suffix = ethers.concat([ - ethers.toBeHex(nonce, 32), + await signer.getAddress(), signature.serialized, ]); @@ -240,9 +240,9 @@ describe('ECDSAMetaTransactionContext', () => { }); describe('#_calldataSuffixLength()', () => { - it('returns 97', async () => { + it('returns 85', async () => { expect(await instance.$_calldataSuffixLength.staticCall()).to.equal( - 97n, + 85n, ); }); }); From 8db59a21e5792e5baa4296927b1fd0a395107ac4 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 17:06:53 -0600 Subject: [PATCH 17/28] update comments --- contracts/meta/_ECDSAMetaTransactionContext.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 54f60e9e..b3a74b1a 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -79,6 +79,8 @@ abstract contract _ECDSAMetaTransactionContext is } } } else { + // calldata is too short for this to be a valid meta transaction + // return message sender as-is msgSender = super._msgSender(); } } @@ -117,6 +119,8 @@ abstract contract _ECDSAMetaTransactionContext is msgData = msg.data[:split]; } else { + // calldata is too short for this to be a valid meta transaction + // return message data as-is msgData = super._msgData(); } } @@ -143,7 +147,7 @@ abstract contract _ECDSAMetaTransactionContext is msgSender = address(bytes20(msg.data[split:split + 20])); bytes calldata signature = msg.data[split + 20:]; - // TODO: lookup nonce + // TODO: lookup and invalidate nonce uint256 nonce = 1; // TODO: include msg.sender in hash to restrict forwarder? @@ -189,8 +193,6 @@ abstract contract _ECDSAMetaTransactionContext is // TODO: see what happens if split calldata v r s address signer = signedHash.tryRecover(signature); - // TODO: invalidate nonce - if (signer == msgSender) { msgDataIndex = split; } else { @@ -202,6 +204,8 @@ abstract contract _ECDSAMetaTransactionContext is bytes32 slot = ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT; assembly { + // it is necessary to store metadata in transient storage because + // subsequent derivation will fail due to none invalidation // TODO: suppress warning // TODO: pack (msgDataIndex as bytes12) tstore(slot, msgSender) From 3650961d2502868205b190fad680f353598897f3 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 17:43:52 -0600 Subject: [PATCH 18/28] use toEIP712RecoverableHash utiltity function in ECDSAMetaTransactionContext --- .../meta/_ECDSAMetaTransactionContext.sol | 35 +++---------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index b3a74b1a..39555669 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -160,38 +160,13 @@ abstract contract _ECDSAMetaTransactionContext is ) ); - bytes32 domainSeparator = EIP712.calculateDomainSeparator_01100(); - - bytes32 signedHash; - - assembly { - // assembly block equivalent to: - // - // signedHash = keccak256( - // abi.encodePacked( - // uint16(0x1901), - // domainSeparator, - // structHash - // ) - // ); - - // load free memory pointer - let pointer := mload(64) - - // this magic value is the EIP-191 signed data header, consisting of - // the hardcoded 0x19 and the one-byte version 0x01 - mstore( - pointer, - 0x1901000000000000000000000000000000000000000000000000000000000000 - ) - mstore(add(pointer, 2), domainSeparator) - mstore(add(pointer, 34), structHash) - - signedHash := keccak256(pointer, 66) - } + bytes32 recoverableHash = ECDSA.toEIP712RecoverableHash( + EIP712.calculateDomainSeparator_01100(), + structHash + ); // TODO: see what happens if split calldata v r s - address signer = signedHash.tryRecover(signature); + address signer = recoverableHash.tryRecover(signature); if (signer == msgSender) { msgDataIndex = split; From 5511b362218ad5eb124f20bc7e434fe947fd6c66 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 20 Mar 2025 18:00:41 -0600 Subject: [PATCH 19/28] fix comment --- contracts/meta/_ECDSAMetaTransactionContext.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 39555669..dcff3f41 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -180,8 +180,7 @@ abstract contract _ECDSAMetaTransactionContext is assembly { // it is necessary to store metadata in transient storage because - // subsequent derivation will fail due to none invalidation - // TODO: suppress warning + // subsequent derivation will fail due to nonce invalidation // TODO: pack (msgDataIndex as bytes12) tstore(slot, msgSender) tstore(add(slot, 1), msgDataIndex) From aa1e2735cb39e20de053ac1a983fd6fbb6e66dfc Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 25 Mar 2025 21:01:33 -0600 Subject: [PATCH 20/28] update pragma version statements --- contracts/interfaces/IERC2771.sol | 2 +- contracts/interfaces/_IERC2771.sol | 2 +- contracts/meta/Context.sol | 2 +- contracts/meta/ECDSAMetaTransactionContext.sol | 2 +- contracts/meta/ForwardedMetaTransactionContext.sol | 2 +- contracts/meta/IContext.sol | 2 +- contracts/meta/IECDSAMetaTransactionContext.sol | 2 +- contracts/meta/IForwardedMetaTransactionContext.sol | 2 +- contracts/meta/_Context.sol | 2 +- contracts/meta/_ECDSAMetaTransactionContext.sol | 2 +- contracts/meta/_ForwardedMetaTransactionContext.sol | 2 +- contracts/meta/_IContext.sol | 2 +- contracts/meta/_IECDSAMetaTransactionContext.sol | 2 +- contracts/meta/_IForwardedMetaTransactionContext.sol | 2 +- contracts/storage/ERC2771Storage.sol | 2 +- tasks/generate_eip_712.ts | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/contracts/interfaces/IERC2771.sol b/contracts/interfaces/IERC2771.sol index ead7e2f2..52e65a3a 100644 --- a/contracts/interfaces/IERC2771.sol +++ b/contracts/interfaces/IERC2771.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { _IERC2771 } from './_IERC2771.sol'; diff --git a/contracts/interfaces/_IERC2771.sol b/contracts/interfaces/_IERC2771.sol index eecfcb36..de6532a9 100644 --- a/contracts/interfaces/_IERC2771.sol +++ b/contracts/interfaces/_IERC2771.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; interface _IERC2771 {} diff --git a/contracts/meta/Context.sol b/contracts/meta/Context.sol index 4b71c514..131db583 100644 --- a/contracts/meta/Context.sol +++ b/contracts/meta/Context.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { IContext } from './IContext.sol'; import { _Context } from './_Context.sol'; diff --git a/contracts/meta/ECDSAMetaTransactionContext.sol b/contracts/meta/ECDSAMetaTransactionContext.sol index 53ea08af..994b97af 100644 --- a/contracts/meta/ECDSAMetaTransactionContext.sol +++ b/contracts/meta/ECDSAMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { IERC5267 } from '../interfaces/IERC5267.sol'; import { Context } from './Context.sol'; diff --git a/contracts/meta/ForwardedMetaTransactionContext.sol b/contracts/meta/ForwardedMetaTransactionContext.sol index 4a2a09d4..a06a673d 100644 --- a/contracts/meta/ForwardedMetaTransactionContext.sol +++ b/contracts/meta/ForwardedMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { IERC2771 } from '../interfaces/IERC2771.sol'; import { Context } from './Context.sol'; diff --git a/contracts/meta/IContext.sol b/contracts/meta/IContext.sol index 8cfd70e1..b089eba4 100644 --- a/contracts/meta/IContext.sol +++ b/contracts/meta/IContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { _IContext } from './_IContext.sol'; diff --git a/contracts/meta/IECDSAMetaTransactionContext.sol b/contracts/meta/IECDSAMetaTransactionContext.sol index 97a769aa..2fdf9d7c 100644 --- a/contracts/meta/IECDSAMetaTransactionContext.sol +++ b/contracts/meta/IECDSAMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { IERC5267 } from '../interfaces/IERC5267.sol'; import { IContext } from './IContext.sol'; diff --git a/contracts/meta/IForwardedMetaTransactionContext.sol b/contracts/meta/IForwardedMetaTransactionContext.sol index 79e83273..ca2dbbb5 100644 --- a/contracts/meta/IForwardedMetaTransactionContext.sol +++ b/contracts/meta/IForwardedMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { IERC2771 } from '../interfaces/IERC2771.sol'; import { IContext } from './IContext.sol'; diff --git a/contracts/meta/_Context.sol b/contracts/meta/_Context.sol index 3b2f6669..7fc4ed8e 100644 --- a/contracts/meta/_Context.sol +++ b/contracts/meta/_Context.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { _IContext } from './_IContext.sol'; diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index dcff3f41..c9f61f66 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { ECDSA } from '../cryptography/ECDSA.sol'; import { EIP712 } from '../cryptography/EIP712.sol'; diff --git a/contracts/meta/_ForwardedMetaTransactionContext.sol b/contracts/meta/_ForwardedMetaTransactionContext.sol index 3cd90d67..c42411bf 100644 --- a/contracts/meta/_ForwardedMetaTransactionContext.sol +++ b/contracts/meta/_ForwardedMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { ERC2771Storage } from '../storage/ERC2771Storage.sol'; import { Address } from '../utils/Address.sol'; diff --git a/contracts/meta/_IContext.sol b/contracts/meta/_IContext.sol index c1a25789..08e10713 100644 --- a/contracts/meta/_IContext.sol +++ b/contracts/meta/_IContext.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; interface _IContext {} diff --git a/contracts/meta/_IECDSAMetaTransactionContext.sol b/contracts/meta/_IECDSAMetaTransactionContext.sol index a9abf4ea..dbf3702a 100644 --- a/contracts/meta/_IECDSAMetaTransactionContext.sol +++ b/contracts/meta/_IECDSAMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { _IERC5267 } from '../interfaces/_IERC5267.sol'; import { _IContext } from './_IContext.sol'; diff --git a/contracts/meta/_IForwardedMetaTransactionContext.sol b/contracts/meta/_IForwardedMetaTransactionContext.sol index 861b3f57..d442deaa 100644 --- a/contracts/meta/_IForwardedMetaTransactionContext.sol +++ b/contracts/meta/_IForwardedMetaTransactionContext.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import { _IERC2771 } from '../interfaces/_IERC2771.sol'; import { _IContext } from './_IContext.sol'; diff --git a/contracts/storage/ERC2771Storage.sol b/contracts/storage/ERC2771Storage.sol index 6b86c25d..a388cfd5 100644 --- a/contracts/storage/ERC2771Storage.sol +++ b/contracts/storage/ERC2771Storage.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; library ERC2771Storage { /** diff --git a/tasks/generate_eip_712.ts b/tasks/generate_eip_712.ts index 6461e15a..1b269d9c 100644 --- a/tasks/generate_eip_712.ts +++ b/tasks/generate_eip_712.ts @@ -7,7 +7,7 @@ const name = 'EIP712'; const filepath = 'cryptography'; const TEMPLATE_SOL = ` -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; /** * @title Procedurally generated EIP-712 typed structured data hashing and signing library From ef68de5b7fd21da6fd080d514b228db36a912c02 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 25 Mar 2025 21:42:17 -0600 Subject: [PATCH 21/28] use Slot for ECDSAMetaTransactionContext --- .../meta/_ECDSAMetaTransactionContext.sol | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index c9f61f66..4aa35e85 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.24; import { ECDSA } from '../cryptography/ECDSA.sol'; import { EIP712 } from '../cryptography/EIP712.sol'; +import { Slot } from '../data/Slot.sol'; +import { Bytes32 } from '../utils/Bytes32.sol'; import { _Context } from './_Context.sol'; import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol'; @@ -11,17 +13,20 @@ abstract contract _ECDSAMetaTransactionContext is _IECDSAMetaTransactionContext, _Context { + using Bytes32 for bytes32; using ECDSA for bytes32; + using Slot for Slot.TransientSlot; bytes32 internal constant EIP_712_TYPE_HASH = keccak256( 'ECDSAMetaTransaction(bytes msgData,uint256 msgValue,uint256 nonce)' ); - bytes32 - internal constant ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT = - keccak256(abi.encode(uint256(EIP_712_TYPE_HASH) - 1)) & - ~bytes32(uint256(0xff)); + Slot.TransientSlot private constant TRANSIENT_SLOT = + Slot.TransientSlot.wrap( + keccak256(abi.encode(uint256(EIP_712_TYPE_HASH) - 1)) & + ~bytes32(uint256(0xff)) + ); function _eip712Domain() internal @@ -65,11 +70,8 @@ abstract contract _ECDSAMetaTransactionContext is // calldata is long enough that it might have a suffix // check transient storage to see if sender has been derived already - bytes32 slot = ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT; - - assembly { - msgSender := tload(slot) - } + // toAddress function strips the packed msgDataIndex data and returns a clean address + msgSender = TRANSIENT_SLOT.read().toAddress(); if (msgSender == address(0)) { // no sender found in transient storage, so attempt to derive it from signature @@ -103,11 +105,8 @@ abstract contract _ECDSAMetaTransactionContext is uint256 split; - bytes32 slot = ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT; - - assembly { - split := tload(add(slot, 1)) - } + // unpack the msgDataIndex which is stored alongside msgSender + split = (TRANSIENT_SLOT.read() >> 160).toUint256(); if (split == 0) { // no msgData split index found in transient storage, so attempt to derive it from signature @@ -176,14 +175,15 @@ abstract contract _ECDSAMetaTransactionContext is } } - bytes32 slot = ECDSA_META_TRANSACTION_CONTEXT_TRANSIENT_STORAGE_SLOT; + bytes32 data; assembly { - // it is necessary to store metadata in transient storage because - // subsequent derivation will fail due to nonce invalidation - // TODO: pack (msgDataIndex as bytes12) - tstore(slot, msgSender) - tstore(add(slot, 1), msgDataIndex) + // pack msgDataIndex into the 12 bytes available alongside msgSender + data := and(msgSender, shl(160, msgDataIndex)) } + + // it is necessary to store metadata in transient storage because + // subsequent derivation will fail due to nonce invalidation + TRANSIENT_SLOT.write(data); } } From 7ea318ed5a0cc56ea4e2a406048ada43d59fdd9e Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 27 Mar 2025 17:25:09 -0600 Subject: [PATCH 22/28] use Bytes32Builder for ECDSAMetaTransactionContext storage --- .../meta/_ECDSAMetaTransactionContext.sol | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 4aa35e85..ad6f62b3 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -6,6 +6,7 @@ import { ECDSA } from '../cryptography/ECDSA.sol'; import { EIP712 } from '../cryptography/EIP712.sol'; import { Slot } from '../data/Slot.sol'; import { Bytes32 } from '../utils/Bytes32.sol'; +import { Bytes32Builder } from '../data/Bytes32Builder.sol'; import { _Context } from './_Context.sol'; import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol'; @@ -14,6 +15,7 @@ abstract contract _ECDSAMetaTransactionContext is _Context { using Bytes32 for bytes32; + using Bytes32Builder for Bytes32Builder.Builder; using ECDSA for bytes32; using Slot for Slot.TransientSlot; @@ -71,7 +73,7 @@ abstract contract _ECDSAMetaTransactionContext is // check transient storage to see if sender has been derived already // toAddress function strips the packed msgDataIndex data and returns a clean address - msgSender = TRANSIENT_SLOT.read().toAddress(); + msgSender = TRANSIENT_SLOT.read().toBuilder().shiftAddress(); if (msgSender == address(0)) { // no sender found in transient storage, so attempt to derive it from signature @@ -106,7 +108,7 @@ abstract contract _ECDSAMetaTransactionContext is uint256 split; // unpack the msgDataIndex which is stored alongside msgSender - split = (TRANSIENT_SLOT.read() >> 160).toUint256(); + split = TRANSIENT_SLOT.read().toBuilder().popUint96(); if (split == 0) { // no msgData split index found in transient storage, so attempt to derive it from signature @@ -175,15 +177,14 @@ abstract contract _ECDSAMetaTransactionContext is } } - bytes32 data; - - assembly { - // pack msgDataIndex into the 12 bytes available alongside msgSender - data := and(msgSender, shl(160, msgDataIndex)) - } - // it is necessary to store metadata in transient storage because // subsequent derivation will fail due to nonce invalidation - TRANSIENT_SLOT.write(data); + + Bytes32Builder.Builder memory builder; + + builder.pushAddress(msgSender); + builder.pushUint96(uint96(msgDataIndex)); + + TRANSIENT_SLOT.write(builder._data); } } From 96aa9b73960de626b9d01300028194bdcd4522b8 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 27 Mar 2025 18:47:50 -0600 Subject: [PATCH 23/28] integrate TransientReentrancyGuard into ECDSAMetaTransactionContext to clear cached context at end of transaction --- .../meta/ECDSAMetaTransactionContext.sol | 31 +++++++++++++++++- .../meta/IECDSAMetaTransactionContext.sol | 2 ++ .../meta/_ECDSAMetaTransactionContext.sol | 32 +++++++++++++++---- .../meta/_IECDSAMetaTransactionContext.sol | 7 +++- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/contracts/meta/ECDSAMetaTransactionContext.sol b/contracts/meta/ECDSAMetaTransactionContext.sol index 994b97af..6f3ba2db 100644 --- a/contracts/meta/ECDSAMetaTransactionContext.sol +++ b/contracts/meta/ECDSAMetaTransactionContext.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.24; import { IERC5267 } from '../interfaces/IERC5267.sol'; +import { TransientReentrancyGuard } from '../security/reentrancy_guard/TransientReentrancyGuard.sol'; +import { _TransientReentrancyGuard } from '../security/reentrancy_guard/_TransientReentrancyGuard.sol'; import { Context } from './Context.sol'; import { _Context } from './_Context.sol'; import { IECDSAMetaTransactionContext } from './IECDSAMetaTransactionContext.sol'; @@ -11,7 +13,8 @@ import { _ECDSAMetaTransactionContext } from './_ECDSAMetaTransactionContext.sol abstract contract ECDSAMetaTransactionContext is IECDSAMetaTransactionContext, _ECDSAMetaTransactionContext, - Context + Context, + TransientReentrancyGuard { /** * @inheritdoc IERC5267 @@ -56,4 +59,30 @@ abstract contract ECDSAMetaTransactionContext is { length = super._calldataSuffixLength(); } + + function _isReentrancyGuardLocked() + internal + view + virtual + override(_TransientReentrancyGuard, TransientReentrancyGuard) + returns (bool status) + { + status = super._isReentrancyGuardLocked(); + } + + function _lockReentrancyGuard() + internal + virtual + override(TransientReentrancyGuard, _ECDSAMetaTransactionContext) + { + super._lockReentrancyGuard(); + } + + function _unlockReentrancyGuard() + internal + virtual + override(_TransientReentrancyGuard, TransientReentrancyGuard) + { + super._unlockReentrancyGuard(); + } } diff --git a/contracts/meta/IECDSAMetaTransactionContext.sol b/contracts/meta/IECDSAMetaTransactionContext.sol index 2fdf9d7c..93795091 100644 --- a/contracts/meta/IECDSAMetaTransactionContext.sol +++ b/contracts/meta/IECDSAMetaTransactionContext.sol @@ -3,11 +3,13 @@ pragma solidity ^0.8.24; import { IERC5267 } from '../interfaces/IERC5267.sol'; +import { ITransientReentrancyGuard } from '../security/reentrancy_guard/ITransientReentrancyGuard.sol'; import { IContext } from './IContext.sol'; import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol'; interface IECDSAMetaTransactionContext is _IECDSAMetaTransactionContext, IContext, + ITransientReentrancyGuard, IERC5267 {} diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index ad6f62b3..896f94ec 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.24; import { ECDSA } from '../cryptography/ECDSA.sol'; import { EIP712 } from '../cryptography/EIP712.sol'; import { Slot } from '../data/Slot.sol'; +import { _TransientReentrancyGuard } from '../security/reentrancy_guard/_TransientReentrancyGuard.sol'; import { Bytes32 } from '../utils/Bytes32.sol'; import { Bytes32Builder } from '../data/Bytes32Builder.sol'; import { _Context } from './_Context.sol'; @@ -12,7 +13,8 @@ import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.s abstract contract _ECDSAMetaTransactionContext is _IECDSAMetaTransactionContext, - _Context + _Context, + _TransientReentrancyGuard { using Bytes32 for bytes32; using Bytes32Builder for Bytes32Builder.Builder; @@ -68,7 +70,11 @@ abstract contract _ECDSAMetaTransactionContext is uint256 dataLength = msg.data.length; uint256 suffixLength = _calldataSuffixLength(); - if (dataLength >= suffixLength) { + // context is cached in transient storage, which is not cleared until the end of the transaction + // this enables the possibility of replay attacks within a single transaction which calls this contract multiple times + // therefore, all functions which use context must be nonReentrant, and the lock must be set before context is accessed + + if (dataLength >= suffixLength && _isReentrancyGuardLocked()) { // calldata is long enough that it might have a suffix // check transient storage to see if sender has been derived already @@ -101,14 +107,16 @@ abstract contract _ECDSAMetaTransactionContext is uint256 dataLength = msg.data.length; uint256 suffixLength = _calldataSuffixLength(); - if (dataLength >= suffixLength) { + // context is cached in transient storage, which is not cleared until the end of the transaction + // this enables the possibility of replay attacks within a single transaction which calls this contract multiple times + // therefore, all functions which use context must be nonReentrant, and the lock must be set before context is accessed + + if (dataLength >= suffixLength && _isReentrancyGuardLocked()) { // calldata is long enough that it might have a suffix // check transient storage to see if msgData split index has been derived already - uint256 split; - // unpack the msgDataIndex which is stored alongside msgSender - split = TRANSIENT_SLOT.read().toBuilder().popUint96(); + uint256 split = TRANSIENT_SLOT.read().toBuilder().popUint96(); if (split == 0) { // no msgData split index found in transient storage, so attempt to derive it from signature @@ -128,7 +136,7 @@ abstract contract _ECDSAMetaTransactionContext is /** * @inheritdoc _Context - * @dev this Context extension defines an address suffix with a length of 85 + * @dev this Context extension defines a suffix with a length of 85 */ function _calldataSuffixLength() internal @@ -187,4 +195,14 @@ abstract contract _ECDSAMetaTransactionContext is TRANSIENT_SLOT.write(builder._data); } + + /** + * @inheritdoc _TransientReentrancyGuard + * @dev clear the cached context to prevent replay attacks + */ + function _lockReentrancyGuard() internal virtual override { + TRANSIENT_SLOT.write(bytes32(0)); + TRANSIENT_SLOT.next().write(bytes32(0)); + super._lockReentrancyGuard(); + } } diff --git a/contracts/meta/_IECDSAMetaTransactionContext.sol b/contracts/meta/_IECDSAMetaTransactionContext.sol index dbf3702a..739f4c4f 100644 --- a/contracts/meta/_IECDSAMetaTransactionContext.sol +++ b/contracts/meta/_IECDSAMetaTransactionContext.sol @@ -3,6 +3,11 @@ pragma solidity ^0.8.24; import { _IERC5267 } from '../interfaces/_IERC5267.sol'; +import { _ITransientReentrancyGuard } from '../security/reentrancy_guard/_ITransientReentrancyGuard.sol'; import { _IContext } from './_IContext.sol'; -interface _IECDSAMetaTransactionContext is _IContext, _IERC5267 {} +interface _IECDSAMetaTransactionContext is + _IContext, + _ITransientReentrancyGuard, + _IERC5267 +{} From cdaed4b459289f027cf3fcb396f8fe838f2d5d81 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Thu, 27 Mar 2025 19:04:54 -0600 Subject: [PATCH 24/28] use Slot clear function on context data, remove duplicate deletion --- contracts/meta/_ECDSAMetaTransactionContext.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 896f94ec..00f70994 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -201,8 +201,7 @@ abstract contract _ECDSAMetaTransactionContext is * @dev clear the cached context to prevent replay attacks */ function _lockReentrancyGuard() internal virtual override { - TRANSIENT_SLOT.write(bytes32(0)); - TRANSIENT_SLOT.next().write(bytes32(0)); + TRANSIENT_SLOT.clear(); super._lockReentrancyGuard(); } } From c8c8cd2f164068160e3d7677a28163f648805d5b Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Sat, 29 Mar 2025 18:46:15 -0600 Subject: [PATCH 25/28] use Bytes32Builder parse functions for context cache --- contracts/meta/_ECDSAMetaTransactionContext.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 00f70994..e1a8afc2 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -79,7 +79,7 @@ abstract contract _ECDSAMetaTransactionContext is // check transient storage to see if sender has been derived already // toAddress function strips the packed msgDataIndex data and returns a clean address - msgSender = TRANSIENT_SLOT.read().toBuilder().shiftAddress(); + msgSender = TRANSIENT_SLOT.read().toBuilder().parseAddress(0); if (msgSender == address(0)) { // no sender found in transient storage, so attempt to derive it from signature @@ -116,7 +116,7 @@ abstract contract _ECDSAMetaTransactionContext is // check transient storage to see if msgData split index has been derived already // unpack the msgDataIndex which is stored alongside msgSender - uint256 split = TRANSIENT_SLOT.read().toBuilder().popUint96(); + uint256 split = TRANSIENT_SLOT.read().toBuilder().parseUint96(160); if (split == 0) { // no msgData split index found in transient storage, so attempt to derive it from signature From 78ac8aa815a687398d98a7a024c67e237f5b2f64 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Fri, 4 Apr 2025 22:04:45 -0600 Subject: [PATCH 26/28] fix access imports and Bytes32.Builder references --- contracts/meta/ECDSAMetaTransactionContext.sol | 4 ++-- contracts/meta/IECDSAMetaTransactionContext.sol | 2 +- contracts/meta/_ECDSAMetaTransactionContext.sol | 6 +++--- contracts/meta/_IECDSAMetaTransactionContext.sol | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/meta/ECDSAMetaTransactionContext.sol b/contracts/meta/ECDSAMetaTransactionContext.sol index 6f3ba2db..afd6e753 100644 --- a/contracts/meta/ECDSAMetaTransactionContext.sol +++ b/contracts/meta/ECDSAMetaTransactionContext.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.24; import { IERC5267 } from '../interfaces/IERC5267.sol'; -import { TransientReentrancyGuard } from '../security/reentrancy_guard/TransientReentrancyGuard.sol'; -import { _TransientReentrancyGuard } from '../security/reentrancy_guard/_TransientReentrancyGuard.sol'; +import { TransientReentrancyGuard } from '../access/reentrancy_guard/TransientReentrancyGuard.sol'; +import { _TransientReentrancyGuard } from '../access/reentrancy_guard/_TransientReentrancyGuard.sol'; import { Context } from './Context.sol'; import { _Context } from './_Context.sol'; import { IECDSAMetaTransactionContext } from './IECDSAMetaTransactionContext.sol'; diff --git a/contracts/meta/IECDSAMetaTransactionContext.sol b/contracts/meta/IECDSAMetaTransactionContext.sol index 93795091..ed9f74fe 100644 --- a/contracts/meta/IECDSAMetaTransactionContext.sol +++ b/contracts/meta/IECDSAMetaTransactionContext.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; import { IERC5267 } from '../interfaces/IERC5267.sol'; -import { ITransientReentrancyGuard } from '../security/reentrancy_guard/ITransientReentrancyGuard.sol'; +import { ITransientReentrancyGuard } from '../access/reentrancy_guard/ITransientReentrancyGuard.sol'; import { IContext } from './IContext.sol'; import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol'; diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index e1a8afc2..4889e1e8 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.24; import { ECDSA } from '../cryptography/ECDSA.sol'; import { EIP712 } from '../cryptography/EIP712.sol'; import { Slot } from '../data/Slot.sol'; -import { _TransientReentrancyGuard } from '../security/reentrancy_guard/_TransientReentrancyGuard.sol'; +import { _TransientReentrancyGuard } from '../access/reentrancy_guard/_TransientReentrancyGuard.sol'; import { Bytes32 } from '../utils/Bytes32.sol'; import { Bytes32Builder } from '../data/Bytes32Builder.sol'; import { _Context } from './_Context.sol'; @@ -17,7 +17,7 @@ abstract contract _ECDSAMetaTransactionContext is _TransientReentrancyGuard { using Bytes32 for bytes32; - using Bytes32Builder for Bytes32Builder.Builder; + using Bytes32Builder for Bytes32.Builder; using ECDSA for bytes32; using Slot for Slot.TransientSlot; @@ -188,7 +188,7 @@ abstract contract _ECDSAMetaTransactionContext is // it is necessary to store metadata in transient storage because // subsequent derivation will fail due to nonce invalidation - Bytes32Builder.Builder memory builder; + Bytes32.Builder memory builder; builder.pushAddress(msgSender); builder.pushUint96(uint96(msgDataIndex)); diff --git a/contracts/meta/_IECDSAMetaTransactionContext.sol b/contracts/meta/_IECDSAMetaTransactionContext.sol index 739f4c4f..d96b5ab9 100644 --- a/contracts/meta/_IECDSAMetaTransactionContext.sol +++ b/contracts/meta/_IECDSAMetaTransactionContext.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; import { _IERC5267 } from '../interfaces/_IERC5267.sol'; -import { _ITransientReentrancyGuard } from '../security/reentrancy_guard/_ITransientReentrancyGuard.sol'; +import { _ITransientReentrancyGuard } from '../access/reentrancy_guard/_ITransientReentrancyGuard.sol'; import { _IContext } from './_IContext.sol'; interface _IECDSAMetaTransactionContext is From d5119cc17864e465eac8772386bfdbc38123a875 Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Fri, 4 Apr 2025 22:04:54 -0600 Subject: [PATCH 27/28] update Solidity barrel file --- contracts/index.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/index.sol b/contracts/index.sol index 82d252b8..9c1256b4 100644 --- a/contracts/index.sol +++ b/contracts/index.sol @@ -112,12 +112,16 @@ import './introspection/Introspectable.sol'; import './introspection/_IIntrospectable.sol'; import './introspection/_Introspectable.sol'; import './meta/Context.sol'; +import './meta/ECDSAMetaTransactionContext.sol'; import './meta/ForwardedMetaTransactionContext.sol'; import './meta/IContext.sol'; +import './meta/IECDSAMetaTransactionContext.sol'; import './meta/IForwardedMetaTransactionContext.sol'; import './meta/_Context.sol'; +import './meta/_ECDSAMetaTransactionContext.sol'; import './meta/_ForwardedMetaTransactionContext.sol'; import './meta/_IContext.sol'; +import './meta/_IECDSAMetaTransactionContext.sol'; import './meta/_IForwardedMetaTransactionContext.sol'; import './proxy/IProxy.sol'; import './proxy/Proxy.sol'; From 72706b39a4c6741f18cef5de0ee6ba317678b7cb Mon Sep 17 00:00:00 2001 From: Nick Barry Date: Tue, 15 Apr 2025 11:14:12 -0600 Subject: [PATCH 28/28] update slot references --- contracts/meta/_ECDSAMetaTransactionContext.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/meta/_ECDSAMetaTransactionContext.sol b/contracts/meta/_ECDSAMetaTransactionContext.sol index 4889e1e8..8fca794c 100644 --- a/contracts/meta/_ECDSAMetaTransactionContext.sol +++ b/contracts/meta/_ECDSAMetaTransactionContext.sol @@ -4,7 +4,8 @@ pragma solidity ^0.8.24; import { ECDSA } from '../cryptography/ECDSA.sol'; import { EIP712 } from '../cryptography/EIP712.sol'; -import { Slot } from '../data/Slot.sol'; +import { sslot } from '../data/StorageSlot.sol'; +import { tslot } from '../data/TransientSlot.sol'; import { _TransientReentrancyGuard } from '../access/reentrancy_guard/_TransientReentrancyGuard.sol'; import { Bytes32 } from '../utils/Bytes32.sol'; import { Bytes32Builder } from '../data/Bytes32Builder.sol'; @@ -19,15 +20,14 @@ abstract contract _ECDSAMetaTransactionContext is using Bytes32 for bytes32; using Bytes32Builder for Bytes32.Builder; using ECDSA for bytes32; - using Slot for Slot.TransientSlot; bytes32 internal constant EIP_712_TYPE_HASH = keccak256( 'ECDSAMetaTransaction(bytes msgData,uint256 msgValue,uint256 nonce)' ); - Slot.TransientSlot private constant TRANSIENT_SLOT = - Slot.TransientSlot.wrap( + tslot private constant TRANSIENT_SLOT = + tslot.wrap( keccak256(abi.encode(uint256(EIP_712_TYPE_HASH) - 1)) & ~bytes32(uint256(0xff)) );