Skip to content
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

EIP-712 transaction type and AA support #3

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
47 changes: 47 additions & 0 deletions contracts/eip712/IAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

Check failure on line 3 in contracts/eip712/IAccount.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Compiler version ^0.8.20 does not satisfy the ^0.5.8 semver requirement

import "./TransactionHelper.sol";

bytes4 constant ACCOUNT_VALIDATION_SUCCESS_MAGIC = IAccount.validateTransaction.selector;

interface IAccount {
/// @notice Called by the bootloader to validate that an account agrees to process the transaction
/// (and potentially pay for it).
/// @param _txHash The hash of the transaction to be used in the explorer
/// @param _suggestedSignedHash The hash of the transaction is signed by EOAs
/// @param _transaction The transaction itself
/// @return magic The magic value that should be equal to the signature of this function
/// if the user agrees to proceed with the transaction.
/// @dev The developer should strive to preserve as many steps as possible both for valid
/// and invalid transactions as this very method is also used during the gas fee estimation
/// (without some of the necessary data, e.g. signature).
function validateTransaction(
bytes32 _txHash,
bytes32 _suggestedSignedHash,
Transaction calldata _transaction
) external payable returns (bytes4 magic);

function executeTransaction(
bytes32 _txHash,
bytes32 _suggestedSignedHash,
Transaction calldata _transaction
) external payable;

// There is no point in providing possible signed hash in the `executeTransactionFromOutside` method,
// since it typically should not be trusted.
function executeTransactionFromOutside(Transaction calldata _transaction) external payable;

function payForTransaction(
bytes32 _txHash,
bytes32 _suggestedSignedHash,
Transaction calldata _transaction
) external payable;

function prepareForPaymaster(
bytes32 _txHash,
bytes32 _possibleSignedHash,
Transaction calldata _transaction
) external payable;
}
53 changes: 53 additions & 0 deletions contracts/eip712/IPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

Check failure on line 3 in contracts/eip712/IPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Compiler version ^0.8.20 does not satisfy the ^0.5.8 semver requirement

import "./TransactionHelper.sol";

enum ExecutionResult {
Revert,
Success
}

bytes4 constant PAYMASTER_VALIDATION_SUCCESS_MAGIC = IPaymaster.validateAndPayForPaymasterTransaction.selector;

interface IPaymaster {
/// @dev Called by the bootloader to verify that the paymaster agrees to pay for the
/// fee for the transaction. This transaction should also send the necessary amount of funds onto the bootloader
/// address.
/// @param _txHash The hash of the transaction
/// @param _suggestedSignedHash The hash of the transaction that is signed by an EOA
/// @param _authenticationSignature The authorization proof signed by an EOA, can be used as a verifiable shortcut
/// @param _transaction The transaction itself.
/// @return magic The value that should be equal to the signature of the validateAndPayForPaymasterTransaction
/// if the paymaster agrees to pay for the transaction.
/// @return context The "context" of the transaction: an array of bytes of length at most 1024 bytes, which will be
/// passed to the `postTransaction` method of the account.
/// @dev The developer should strive to preserve as many steps as possible both for valid
/// and invalid transactions as this very method is also used during the gas fee estimation
/// (without some of the necessary data, e.g. signature).
function validateAndPayForPaymasterTransaction(
bytes32 _txHash,
bytes32 _suggestedSignedHash,
bytes memory _authenticationSignature,
Transaction calldata _transaction
) external payable returns (bytes4 magic, bytes memory context);

/// @dev Called by the bootloader after the execution of the transaction. Please note that
/// there is no guarantee that this method will be called at all. Unlike the original EIP4337,
/// this method won't be called if the transaction execution results in out-of-gas.
/// @param _context, the context of the execution, returned by the "validateAndPayForPaymasterTransaction" method.
/// @param _transaction, the users' transaction.
/// @param _txResult, the result of the transaction execution (success or failure).
/// @param _maxRefundedGas, the upper bound on the amout of gas that could be refunded to the paymaster.
/// @dev The exact amount refunded depends on the gas spent by the "postOp" itself and so the developers should
/// take that into account.
function postTransaction(
bytes calldata _context,
Transaction calldata _transaction,
bytes32 _txHash,
bytes32 _suggestedSignedHash,
ExecutionResult _txResult,
uint256 _maxRefundedGas
) external payable;
}
17 changes: 17 additions & 0 deletions contracts/eip712/IPaymasterFlow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

Check failure on line 3 in contracts/eip712/IPaymasterFlow.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Compiler version ^0.8.20 does not satisfy the ^0.5.8 semver requirement

/**
* @author Matter Labs
* @custom:security-contact [email protected]
* @dev The interface that is used for encoding/decoding of
* different types of paymaster flows.
* @notice This is NOT an interface to be implementated
* by contracts. It is just used for encoding.
*/
interface IPaymasterFlow {
function general(bytes calldata input) external;

function approvalBased(address _token, uint256 _minAllowance, bytes calldata _innerInput) external;
}
54 changes: 54 additions & 0 deletions contracts/eip712/TestPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

Check failure on line 2 in contracts/eip712/TestPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Compiler version ^0.8.20 does not satisfy the ^0.5.8 semver requirement

import "./IPaymaster.sol";
import "../libraries/EIP712.sol";
import "../libraries/ECDSA.sol";

contract TestPaymaster is IPaymaster, EIP712 {
struct SignData {
bytes32 suggestedSignedHash;
}

address public owner;

constructor(string memory name, string memory version) EIP712(name, version) {

Check warning on line 15 in contracts/eip712/TestPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Explicitly mark visibility in function (Set ignoreConstructors to true if using solidity >=0.7.0)
owner = msg.sender;
}

function signDataTypeHash() internal pure returns (bytes32) {
return keccak256("SignData(bytes32 suggestedSignedHash)");
}

function signDataMessage(bytes32 suggestedSignedHash) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(
signDataTypeHash(),
suggestedSignedHash
)));
}

function validateAndPayForPaymasterTransaction(
bytes32 _txHash,

Check warning on line 31 in contracts/eip712/TestPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Variable "_txHash" is unused
bytes32 _suggestedSignedHash,
bytes memory _authenticationSignature,

Check warning on line 33 in contracts/eip712/TestPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Variable "_authenticationSignature" is unused
Transaction calldata _transaction

Check warning on line 34 in contracts/eip712/TestPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Variable "_transaction" is unused
) external payable returns (bytes4 magic, bytes memory context) {
if (_suggestedSignedHash == 0x0) {
return (0x00000001, context);
} else {
require(_suggestedSignedHash != bytes32(uint256(1)), "Paymaster: Test revert");
}
return (PAYMASTER_VALIDATION_SUCCESS_MAGIC, context);
}

function postTransaction(
bytes calldata _context,
Transaction calldata _transaction,
bytes32 _txHash,
bytes32 _suggestedSignedHash,
ExecutionResult _txResult,
uint256 _maxRefundedGas
) external payable {

Check warning on line 51 in contracts/eip712/TestPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Code contains empty blocks

}
}
172 changes: 172 additions & 0 deletions contracts/eip712/TransactionHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

Check failure on line 3 in contracts/eip712/TransactionHelper.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Compiler version 0.8.20 does not satisfy the ^0.5.8 semver requirement

import "../openzeppelin/token/ERC20/IERC20.sol";
import "../openzeppelin/token/ERC20/utils/SafeERC20.sol";

import "./IPaymasterFlow.sol";

/// @dev The type id of U2U's EIP-712-signed transaction.
uint8 constant EIP_712_TX_TYPE = 0x71;

/// @dev The type id of legacy transactions.
uint8 constant LEGACY_TX_TYPE = 0x0;
/// @dev The type id of legacy transactions.
uint8 constant EIP_2930_TX_TYPE = 0x01;
/// @dev The type id of EIP1559 transactions.
uint8 constant EIP_1559_TX_TYPE = 0x02;

address constant U2U_TOKEN = address(0xA99cf32e9aAa700f9E881BA9BF2C57A211ae94df);

/// @notice Structure used to represent a U2U transaction.
struct Transaction {
// The type of the transaction.
uint256 txType;
// The caller.
uint256 from;
// The callee.
uint256 to;
// The gasLimit to pass with the transaction.
// It has the same meaning as Ethereum's gasLimit.
uint256 gasLimit;
// The maximum amount of gas the user is willing to pay for a byte of pubdata.
uint256 gasPerPubdataByteLimit;
// The maximum fee per gas that the user is willing to pay.
// It is akin to EIP1559's maxFeePerGas.
uint256 maxFeePerGas;
// The maximum priority fee per gas that the user is willing to pay.
// It is akin to EIP1559's maxPriorityFeePerGas.
uint256 maxPriorityFeePerGas;
// The transaction's paymaster. If there is no paymaster, it is equal to 0.
uint256 paymaster;
// The nonce of the transaction.
uint256 nonce;
// The value to pass with the transaction.
uint256 value;
// In the future, we might want to add some
// new fields to the struct. The `txData` struct
// is to be passed to account and any changes to its structure
// would mean a breaking change to these accounts. In order to prevent this,
// we should keep some fields as "reserved".
// It is also recommended that their length is fixed, since
// it would allow easier proof integration (in case we will need
// some special circuit for preprocessing transactions).
uint256[4] reserved;
// The transaction's calldata.
bytes data;
// The signature of the transaction.
bytes signature;
// The input to the paymaster.
bytes paymasterInput;
}

/**
* @author Matter Labs
* @custom:security-contact [email protected]
* @notice Library is used to help custom accounts to work with common methods for the Transaction type.
*/
library TransactionHelper {
using SafeERC20 for IERC20;

/// @notice The EIP-712 typehash for the contract's domain
bytes32 constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId)");

Check warning on line 73 in contracts/eip712/TransactionHelper.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Explicitly mark visibility of state

bytes32 constant EIP712_TRANSACTION_TYPE_HASH =

Check warning on line 75 in contracts/eip712/TransactionHelper.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Explicitly mark visibility of state
keccak256(
"Transaction(uint256 txType,uint256 from,uint256 to,uint256 gasLimit,uint256 gasPerPubdataByteLimit,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,uint256 paymaster,uint256 nonce,uint256 value,bytes data,bytes paymasterInput)"
);

/// @notice Whether the token is Ethereum.
/// @param _addr The address of the token
/// @return `true` or `false` based on whether the token is Ether.
/// @dev This method assumes that address is Ether either if the address is 0 (for convenience)
/// or if the address is the address of the L2EthToken system contract.
function isU2UToken(uint256 _addr) internal pure returns (bool) {
return _addr == uint256(uint160(address(U2U_TOKEN))) || _addr == 0;
}

/// @notice Calculate the suggested signed hash of the transaction,
/// i.e. the hash that is signed by EOAs and is recommended to be signed by other accounts.
function encodeHash(Transaction calldata _transaction) internal view returns (bytes32 resultHash) {
if (_transaction.txType == EIP_712_TX_TYPE) {
resultHash = _encodeHashEIP712Transaction(_transaction);
} else {
// Currently no other transaction types are supported.
// Any new transaction types will be processed in a similar manner.
revert("Encoding unsupported tx");
}
}

/// @notice Encode hash of the U2U native transaction type.
/// @return keccak256 hash of the EIP-712 encoded representation of transaction
function _encodeHashEIP712Transaction(Transaction calldata _transaction) private view returns (bytes32) {
bytes32 structHash = keccak256(
abi.encode(
EIP712_TRANSACTION_TYPE_HASH,
_transaction.txType,
_transaction.from,
_transaction.to,
_transaction.gasLimit,
_transaction.gasPerPubdataByteLimit,
_transaction.maxFeePerGas,
_transaction.maxPriorityFeePerGas,
_transaction.paymaster,
_transaction.nonce,
_transaction.value,
keccak256(_transaction.data),
keccak256(_transaction.paymasterInput)
)
);

bytes32 domainSeparator = keccak256(
abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256("U2U"), keccak256("2"), block.chainid)
);

return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}

/// @notice Processes the common paymaster flows, e.g. setting proper allowance
/// for tokens, etc. For more information on the expected behavior, check out
/// the "Paymaster flows" section in the documentation.
function processPaymasterInput(Transaction calldata _transaction) internal {
require(_transaction.paymasterInput.length >= 4, "The standard paymaster input must be at least 4 bytes long");

Check warning on line 133 in contracts/eip712/TransactionHelper.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Error message for require is too long

bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]);
if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) {
require(
_transaction.paymasterInput.length >= 68,
"The approvalBased paymaster input must be at least 68 bytes long"
);

// While the actual data consists of address, uint256 and bytes data,
// the data is needed only for the paymaster, so we ignore it here for the sake of optimization
(address token, uint256 minAllowance) = abi.decode(_transaction.paymasterInput[4:68], (address, uint256));
address paymaster = address(uint160(_transaction.paymaster));

uint256 currentAllowance = IERC20(token).allowance(address(this), paymaster);
if (currentAllowance < minAllowance) {
// Some tokens, e.g. USDT require that the allowance is firsty set to zero
// and only then updated to the new value.

IERC20(token).safeApprove(paymaster, 0);
IERC20(token).safeApprove(paymaster, minAllowance);
}
} else if (paymasterInputSelector == IPaymasterFlow.general.selector) {
// Do nothing. general(bytes) paymaster flow means that the paymaster must interpret these bytes on his own.
} else {
revert("Unsupported paymaster flow");
}
}

// Returns the balance required to process the transaction.
function totalRequiredBalance(Transaction calldata _transaction) internal pure returns (uint256 requiredBalance) {
if (address(uint160(_transaction.paymaster)) != address(0)) {
// Paymaster pays for the fee
requiredBalance = _transaction.value;
} else {
// The user should have enough balance for both the fee and the value of the transaction
requiredBalance = _transaction.maxFeePerGas * _transaction.gasLimit + _transaction.value;
}
}
}
Loading
Loading