Skip to content

Commit

Permalink
Init EIP-712 interfaces and libs
Browse files Browse the repository at this point in the history
  • Loading branch information
trinhdn97 committed Mar 6, 2024
1 parent 8ff0321 commit cfb2f70
Show file tree
Hide file tree
Showing 8 changed files with 888 additions and 0 deletions.
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 "../libraries/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;
}
51 changes: 51 additions & 0 deletions contracts/eip712/IPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 "../libraries/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 _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,
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;
}
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(

Check warning on line 137 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
_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) {

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

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

Code contains empty blocks
// 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;
}
}
}
82 changes: 82 additions & 0 deletions contracts/openzeppelin/token/ERC20/IERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

Check failure on line 4 in contracts/openzeppelin/token/ERC20/IERC20.sol

View workflow job for this annotation

GitHub Actions / Lint sources (18.16.1)

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

/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);

/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);

/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);

/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);

/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);

/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);

/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);

/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
Loading

0 comments on commit cfb2f70

Please sign in to comment.