Skip to content

Add ECDSAMetaTransactionContext #290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 50 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
a6d7b71
Merge branch 'crypto-improvements' into ecdsa-context
ItsNickBarry Mar 20, 2025
c9c3e70
remove view restriction from context functions
ItsNickBarry Mar 20, 2025
66061bd
Merge branch 'msg-sender-trick' into ecdsa-context
ItsNickBarry Mar 20, 2025
be3f803
Merge branch 'msg-sender-trick' into ecdsa-context
ItsNickBarry Mar 20, 2025
670d4fc
Merge branch 'msg-sender-trick' into ecdsa-context
ItsNickBarry Mar 20, 2025
d36ebea
remove view restrictions from functions that call context functions
ItsNickBarry Mar 20, 2025
26137ac
set evmVerion to cancun
ItsNickBarry Mar 20, 2025
a7510d4
add draft ECDSAMetaTransactionContext contracts
ItsNickBarry Mar 20, 2025
00776de
Merge branch 'msg-sender-trick' into ecdsa-context
ItsNickBarry Mar 20, 2025
b185260
use non-reverting recover for ECDSA context
ItsNickBarry Mar 20, 2025
4d633a0
fix _eip712Domain output
ItsNickBarry Mar 20, 2025
214fff9
Merge branch 'crypto-improvements' into ecdsa-context
ItsNickBarry Mar 20, 2025
5bef448
reference ERC5267 fields constant in ECDSAMetaTransactionContext
ItsNickBarry Mar 20, 2025
32e632d
fix hash struct procedure
ItsNickBarry Mar 20, 2025
86662f6
test ECDSAMetaTransactionContext
ItsNickBarry Mar 20, 2025
be59343
fix test nesting
ItsNickBarry Mar 20, 2025
054d056
use semi-standardized transient storage slot
ItsNickBarry Mar 20, 2025
6087a18
support msg.value in ECDSAMetaTransactionContext
ItsNickBarry Mar 20, 2025
e71e5e9
add failing nonce revert tests
ItsNickBarry Mar 20, 2025
d1169a5
test incorrect msg.value
ItsNickBarry Mar 20, 2025
c88d411
fix comment
ItsNickBarry Mar 20, 2025
5bba739
replae nonce in suffix with expected signer address
ItsNickBarry Mar 20, 2025
8db59a2
update comments
ItsNickBarry Mar 20, 2025
16328d2
Merge branch 'crypto-improvements' into ecdsa-context
ItsNickBarry Mar 20, 2025
3650961
use toEIP712RecoverableHash utiltity function in ECDSAMetaTransaction…
ItsNickBarry Mar 20, 2025
5511b36
fix comment
ItsNickBarry Mar 21, 2025
d440b5f
Merge branch 'storage-utils-improvements' into ecdsa-context
ItsNickBarry Mar 26, 2025
aa1e273
update pragma version statements
ItsNickBarry Mar 26, 2025
ef68de5
use Slot for ECDSAMetaTransactionContext
ItsNickBarry Mar 26, 2025
f83a461
Merge branch 'master' into ecdsa-context
ItsNickBarry Mar 27, 2025
89cce85
Merge branch 'msg-sender-trick' into ecdsa-context
ItsNickBarry Mar 27, 2025
7e3bad9
Merge branch 'bytes32-builder' into ecdsa-context
ItsNickBarry Mar 27, 2025
ede6b0a
Merge branch 'bytes32-builder' into ecdsa-context
ItsNickBarry Mar 27, 2025
7ea318e
use Bytes32Builder for ECDSAMetaTransactionContext storage
ItsNickBarry Mar 27, 2025
96aa9b7
integrate TransientReentrancyGuard into ECDSAMetaTransactionContext t…
ItsNickBarry Mar 28, 2025
54f2466
Merge branch 'slot-delete' into ecdsa-context
ItsNickBarry Mar 28, 2025
cdaed4b
use Slot clear function on context data, remove duplicate deletion
ItsNickBarry Mar 28, 2025
4d463e5
Merge branch 'master' into ecdsa-context
ItsNickBarry Mar 28, 2025
69ff4da
Merge branch 'bytes32-builder' into ecdsa-context
ItsNickBarry Mar 29, 2025
7e4d2ea
Merge branch 'master' into ecdsa-context
ItsNickBarry Mar 30, 2025
6ce5f66
Merge branch 'bytes32-builder' into ecdsa-context
ItsNickBarry Mar 30, 2025
c8c8cd2
use Bytes32Builder parse functions for context cache
ItsNickBarry Mar 30, 2025
f5ee530
Merge branch 'master' into ecdsa-context
ItsNickBarry Mar 30, 2025
08f2641
Merge branch 'master' into ecdsa-context
ItsNickBarry Apr 2, 2025
acfd4f7
Merge branch 'master' into ecdsa-context
ItsNickBarry Apr 5, 2025
78ac8aa
fix access imports and Bytes32.Builder references
ItsNickBarry Apr 5, 2025
d5119cc
update Solidity barrel file
ItsNickBarry Apr 5, 2025
166653f
Merge branch 'master' into ecdsa-context
ItsNickBarry Apr 15, 2025
72706b3
update slot references
ItsNickBarry Apr 15, 2025
76217e6
Merge branch 'master' into ecdsa-context
ItsNickBarry Apr 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/access/access_control/_AccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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());
}

Expand Down
4 changes: 4 additions & 0 deletions contracts/index.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,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';
Expand Down
88 changes: 88 additions & 0 deletions contracts/meta/ECDSAMetaTransactionContext.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { IERC5267 } from '../interfaces/IERC5267.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';
import { _ECDSAMetaTransactionContext } from './_ECDSAMetaTransactionContext.sol';

abstract contract ECDSAMetaTransactionContext is
IECDSAMetaTransactionContext,
_ECDSAMetaTransactionContext,
Context,
TransientReentrancyGuard
{
/**
* @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();
}

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();
}
}
2 changes: 0 additions & 2 deletions contracts/meta/ForwardedMetaTransactionContext.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ abstract contract ForwardedMetaTransactionContext is
*/
function _msgSender()
internal
view
virtual
override(_Context, _ForwardedMetaTransactionContext)
returns (address msgSender)
Expand All @@ -40,7 +39,6 @@ abstract contract ForwardedMetaTransactionContext is
*/
function _msgData()
internal
view
virtual
override(_Context, _ForwardedMetaTransactionContext)
returns (bytes calldata msgData)
Expand Down
15 changes: 15 additions & 0 deletions contracts/meta/IECDSAMetaTransactionContext.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { IERC5267 } from '../interfaces/IERC5267.sol';
import { ITransientReentrancyGuard } from '../access/reentrancy_guard/ITransientReentrancyGuard.sol';
import { IContext } from './IContext.sol';
import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol';

interface IECDSAMetaTransactionContext is
_IECDSAMetaTransactionContext,
IContext,
ITransientReentrancyGuard,
IERC5267
{}
4 changes: 2 additions & 2 deletions contracts/meta/_Context.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down
207 changes: 207 additions & 0 deletions contracts/meta/_ECDSAMetaTransactionContext.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import { ECDSA } from '../cryptography/ECDSA.sol';
import { EIP712 } from '../cryptography/EIP712.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';
import { _Context } from './_Context.sol';
import { _IECDSAMetaTransactionContext } from './_IECDSAMetaTransactionContext.sol';

abstract contract _ECDSAMetaTransactionContext is
_IECDSAMetaTransactionContext,
_Context,
_TransientReentrancyGuard
{
using Bytes32 for bytes32;
using Bytes32Builder for Bytes32.Builder;
using ECDSA for bytes32;

bytes32 internal constant EIP_712_TYPE_HASH =
keccak256(
'ECDSAMetaTransaction(bytes msgData,uint256 msgValue,uint256 nonce)'
);

tslot private constant TRANSIENT_SLOT =
tslot.wrap(
keccak256(abi.encode(uint256(EIP_712_TYPE_HASH) - 1)) &
~bytes32(uint256(0xff))
);

function _eip712Domain()
internal
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
return (
EIP712.ERC5267_FIELDS_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();

// 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

// toAddress function strips the packed msgDataIndex data and returns a clean address
msgSender = TRANSIENT_SLOT.read().toBuilder().parseAddress(0);

if (msgSender == address(0)) {
// no sender found in transient storage, so attempt to derive it from signature

unchecked {
(msgSender, ) = _processCalldata(dataLength - suffixLength);
}
}
} else {
// calldata is too short for this to be a valid meta transaction
// return message sender as-is
msgSender = super._msgSender();
}
}

/**
* @inheritdoc _Context
*/
function _msgData()
internal
virtual
override
returns (bytes calldata msgData)
{
uint256 dataLength = msg.data.length;
uint256 suffixLength = _calldataSuffixLength();

// 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

// unpack the msgDataIndex which is stored alongside msgSender
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

unchecked {
(, split) = _processCalldata(dataLength - suffixLength);
}
}

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();
}
}

/**
* @inheritdoc _Context
* @dev this Context extension defines a suffix with a length of 85
*/
function _calldataSuffixLength()
internal
view
virtual
override
returns (uint256 length)
{
length = 85;
}

function _processCalldata(
uint256 split
) private returns (address msgSender, uint256 msgDataIndex) {
unchecked {
bytes calldata msgData = msg.data[:split];
msgSender = address(bytes20(msg.data[split:split + 20]));
bytes calldata signature = msg.data[split + 20:];

// TODO: lookup and invalidate nonce
uint256 nonce = 1;

// TODO: include msg.sender in hash to restrict forwarder?
bytes32 structHash = keccak256(
abi.encode(
EIP_712_TYPE_HASH,
keccak256(msgData),
msg.value,
nonce
)
);

bytes32 recoverableHash = ECDSA.toEIP712RecoverableHash(
EIP712.calculateDomainSeparator_01100(),
structHash
);

// TODO: see what happens if split calldata v r s
address signer = recoverableHash.tryRecover(signature);

if (signer == msgSender) {
msgDataIndex = split;
} else {
msgSender = super._msgSender();
msgDataIndex = super._msgData().length;
}
}

// it is necessary to store metadata in transient storage because
// subsequent derivation will fail due to nonce invalidation

Bytes32.Builder memory builder;

builder.pushAddress(msgSender);
builder.pushUint96(uint96(msgDataIndex));

TRANSIENT_SLOT.write(builder._data);
}

/**
* @inheritdoc _TransientReentrancyGuard
* @dev clear the cached context to prevent replay attacks
*/
function _lockReentrancyGuard() internal virtual override {
TRANSIENT_SLOT.clear();
super._lockReentrancyGuard();
}
}
2 changes: 0 additions & 2 deletions contracts/meta/_ForwardedMetaTransactionContext.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ abstract contract _ForwardedMetaTransactionContext is
*/
function _msgSender()
internal
view
virtual
override
returns (address msgSender)
Expand All @@ -46,7 +45,6 @@ abstract contract _ForwardedMetaTransactionContext is
*/
function _msgData()
internal
view
virtual
override
returns (bytes calldata msgData)
Expand Down
Loading