Transaction Hash:
Block:
22077807 at Mar-19-2025 02:02:23 AM +UTC
Transaction Fee:
0.0000297066 ETH
$0.08
Gas Used:
49,511 Gas / 0.6 Gwei
Emitted Events:
916 |
DxToken.Transfer( from=[Sender] 0xd3f45e20cb8ec2d65aac5797ceabf989798f58fa, to=AmbireAccount, value=850000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 17.314677874022959724 Eth | 17.314682018881330223 Eth | 0.000004144858370499 | |
0x973e5269...8d27041A9 | |||||
0xd3f45e20...9798F58Fa |
0.007980861497173124 Eth
Nonce: 115
|
0.007951154897173124 Eth
Nonce: 116
| 0.0000297066 |
Execution Trace
DxToken.transfer( _to=0xe6A37f5C75eDCC162f397be7C649a50FC351D597, _value=850000000000000000000 ) => ( True )
transfer[ERC20Basic (ln:156)]
File 1 of 2: DxToken
File 2 of 2: AmbireAccount
pragma solidity ^0.4.23; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { if (a == 0) { return 0; } c = a * b; assert(c / a == b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 // uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return a / b; } /** * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256 c) { c = a + b; assert(c >= a); return c; } } /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipRenounced(address indexed previousOwner); event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); emit OwnershipTransferred(owner, newOwner); owner = newOwner; } /** * @dev Allows the current owner to relinquish control of the contract. */ function renounceOwnership() public onlyOwner { emit OwnershipRenounced(owner); owner = address(0); } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is Ownable { event Pause(); event Unpause(); bool public paused = false; /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyOwner whenNotPaused public { paused = true; emit Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; emit Unpause(); } } /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/179 */ contract ERC20Basic { function totalSupply() public view returns (uint256); function balanceOf(address who) public view returns (uint256); function transfer(address to, uint256 value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public view returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval( address indexed owner, address indexed spender, uint256 value ); } /** * @title Basic token * @dev Basic version of StandardToken, with no allowances. */ contract BasicToken is ERC20Basic { using SafeMath for uint256; mapping(address => uint256) balances; uint256 totalSupply_; /** * @dev total number of tokens in existence */ function totalSupply() public view returns (uint256) { return totalSupply_; } /** * @dev transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(_value <= balances[msg.sender]); balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); emit Transfer(msg.sender, _to, _value); return true; } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; } } /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * @dev https://github.com/ethereum/EIPs/issues/20 * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract StandardToken is ERC20, BasicToken { mapping (address => mapping (address => uint256)) internal allowed; /** * @dev Transfer tokens from one address to another * @param _from address The address which you want to send tokens from * @param _to address The address which you want to transfer to * @param _value uint256 the amount of tokens to be transferred */ function transferFrom( address _from, address _to, uint256 _value ) public returns (bool) { require(_to != address(0)); require(_value <= balances[_from]); require(_value <= allowed[_from][msg.sender]); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); emit Transfer(_from, _to, _value); return true; } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * * 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 * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint256 _value) public returns (bool) { allowed[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } /** * @dev Function to check the amount of tokens that an owner allowed to a spender. * @param _owner address The address which owns the funds. * @param _spender address The address which will spend the funds. * @return A uint256 specifying the amount of tokens still available for the spender. */ function allowance( address _owner, address _spender ) public view returns (uint256) { return allowed[_owner][_spender]; } /** * @dev Increase the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To increment * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param _spender The address which will spend the funds. * @param _addedValue The amount of tokens to increase the allowance by. */ function increaseApproval( address _spender, uint _addedValue ) public returns (bool) { allowed[msg.sender][_spender] = ( allowed[msg.sender][_spender].add(_addedValue)); emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev Decrease the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To decrement * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param _spender The address which will spend the funds. * @param _subtractedValue The amount of tokens to decrease the allowance by. */ function decreaseApproval( address _spender, uint _subtractedValue ) public returns (bool) { uint oldValue = allowed[msg.sender][_spender]; if (_subtractedValue > oldValue) { allowed[msg.sender][_spender] = 0; } else { allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); } emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } } /** * @title Pausable token * @dev StandardToken modified with pausable transfers. **/ contract PausableToken is StandardToken, Pausable { function transfer( address _to, uint256 _value ) public whenNotPaused returns (bool) { return super.transfer(_to, _value); } function transferFrom( address _from, address _to, uint256 _value ) public whenNotPaused returns (bool) { return super.transferFrom(_from, _to, _value); } function approve( address _spender, uint256 _value ) public whenNotPaused returns (bool) { return super.approve(_spender, _value); } function increaseApproval( address _spender, uint _addedValue ) public whenNotPaused returns (bool success) { return super.increaseApproval(_spender, _addedValue); } function decreaseApproval( address _spender, uint _subtractedValue ) public whenNotPaused returns (bool success) { return super.decreaseApproval(_spender, _subtractedValue); } } contract DxToken is PausableToken { string public name = "DxChain Token"; string public symbol = "DX"; uint public decimals = 18; uint public INITIAL_SUPPLY = 10**29; constructor() public { totalSupply_ = INITIAL_SUPPLY; balances[msg.sender] = INITIAL_SUPPLY; } }
File 2 of 2: AmbireAccount
// SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; import './libs/SignatureValidator.sol'; import './ExternalSigValidator.sol'; import './libs/erc4337/PackedUserOperation.sol'; import './libs/erc4337/UserOpHelper.sol'; import './deployless/IAmbireAccount.sol'; /** * @notice A validator that performs DKIM signature recovery * @dev All external/public functions (that are not view/pure) use `payable` because AmbireAccount * is a wallet contract, and any ETH sent to it is not lost, but on the other hand not having `payable` * makes the Solidity compiler add an extra check for `msg.value`, which in this case is wasted gas */ contract AmbireAccount is IAmbireAccount { \t// @dev We do not have a constructor. This contract cannot be initialized with any valid `privileges` by itself! \t// The intended use case is to deploy one base implementation contract, and create a minimal proxy for each user wallet, by \t// using our own code generation to insert SSTOREs to initialize `privileges` (it was previously called IdentityProxyDeploy.js, now src/libs/proxyDeploy/deploy.ts) \taddress private constant FALLBACK_HANDLER_SLOT = address(0x6969); \t// @dev This is how we understand if msg.sender is the entry point \tbytes32 private constant ENTRY_POINT_MARKER = 0x0000000000000000000000000000000000000000000000000000000000007171; \t// Externally validated signatures \tuint8 private constant SIGMODE_EXTERNALLY_VALIDATED = 255; \t// Variables \tmapping(address => bytes32) public privileges; \tuint256 public nonce; \t// Events \tevent LogPrivilegeChanged(address indexed addr, bytes32 priv); \tevent LogErr(address indexed to, uint256 value, bytes data, bytes returnData); // only used in tryCatch \t// This contract can accept ETH without calldata \treceive() external payable {} \t/** \t * @dev To support EIP 721 and EIP 1155, we need to respond to those methods with their own method signature \t * @return bytes4 onERC721Received function selector \t */ \tfunction onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { \t\treturn this.onERC721Received.selector; \t} \t/** \t * @dev To support EIP 721 and EIP 1155, we need to respond to those methods with their own method signature \t * @return bytes4 onERC1155Received function selector \t */ \tfunction onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { \t\treturn this.onERC1155Received.selector; \t} \t/** \t * @dev To support EIP 721 and EIP 1155, we need to respond to those methods with their own method signature \t * @return bytes4 onERC1155Received function selector \t */ \tfunction onERC1155BatchReceived( \t\taddress, \t\taddress, \t\tuint256[] calldata, \t\tuint256[] calldata, \t\tbytes calldata \t) external pure returns (bytes4) { \t\treturn this.onERC1155BatchReceived.selector; \t} \t/** \t * @notice fallback method: currently used to call the fallback handler \t * which is set by the user and can be changed \t * @dev this contract can accept ETH with calldata, hence payable \t */ \tfallback() external payable { \t\t// We store the fallback handler at this magic slot \t\taddress fallbackHandler = address(uint160(uint(privileges[FALLBACK_HANDLER_SLOT]))); \t\tif (fallbackHandler == address(0)) return; \t\tassembly { \t\t\t// we can use addr 0 because logic is taking full control of the \t\t\t// execution making sure it returns itself and does not \t\t\t// rely on any further Solidity code. \t\t\tcalldatacopy(0, 0, calldatasize()) \t\t\tlet result := delegatecall(gas(), fallbackHandler, 0, calldatasize(), 0, 0) \t\t\tlet size := returndatasize() \t\t\treturndatacopy(0, 0, size) \t\t\tif eq(result, 0) { \t\t\t\trevert(0, size) \t\t\t} \t\t\treturn(0, size) \t\t} \t} \t/** \t * @notice used to set the privilege of a key (by `addr`) \t * @dev normal signatures will be considered valid if the \t * `addr` they are signed with has non-zero (not 0x000..000) privilege set; we can set the privilege to \t * a hash of the recovery keys and timelock (see `RecoveryInfo`) to enable recovery signatures \t * @param addr the address to give privs to \t * @param priv the privs to give \t */ \tfunction setAddrPrivilege(address addr, bytes32 priv) external payable { \t\trequire(msg.sender == address(this), 'ONLY_ACCOUNT_CAN_CALL'); \t\tprivileges[addr] = priv; \t\temit LogPrivilegeChanged(addr, priv); \t} \t/** \t * @notice Useful when we need to do multiple operations but ignore failures in some of them \t * @param to address we're sending value to \t * @param value the amount \t * @param data callData \t */ \tfunction tryCatch(address to, uint256 value, bytes calldata data) external payable { \t\trequire(msg.sender == address(this), 'ONLY_ACCOUNT_CAN_CALL'); \t\tuint256 gasBefore = gasleft(); \t\t(bool success, bytes memory returnData) = to.call{ value: value, gas: gasBefore }(data); \t\trequire(gasleft() > gasBefore / 64, 'TRYCATCH_OOG'); \t\tif (!success) emit LogErr(to, value, data, returnData); \t} \t/** \t * @notice same as `tryCatch` but with a gas limit \t * @param to address we're sending value to \t * @param value the amount \t * @param data callData \t * @param gasLimit how much gas is allowed \t */ \tfunction tryCatchLimit(address to, uint256 value, bytes calldata data, uint256 gasLimit) external payable { \t\trequire(msg.sender == address(this), 'ONLY_ACCOUNT_CAN_CALL'); \t\tuint256 gasBefore = gasleft(); \t\t(bool success, bytes memory returnData) = to.call{ value: value, gas: gasLimit }(data); \t\trequire(gasleft() > gasBefore / 64, 'TRYCATCH_OOG'); \t\tif (!success) emit LogErr(to, value, data, returnData); \t} \t/** \t * @notice execute: this method is used to execute a single bundle of calls that are signed with a key \t * that is authorized to execute on this account (in `privileges`) \t * @dev WARNING: if the signature of this is changed, we have to change AmbireAccountFactory \t * @param calls the transaction we're executing. They may not execute \t * if specific cases. One such is when setting a timelock \t * @param signature the signature for the transactions \t */ \tfunction execute(Transaction[] calldata calls, bytes calldata signature) public payable { \t\taddress signerKey; \t\tuint8 sigMode = uint8(signature[signature.length - 1]); \t\tuint256 currentNonce = nonce; \t\t// we increment the nonce here (not using `nonce++` to save some gas) \t\tnonce = currentNonce + 1; \t\tif (sigMode == SIGMODE_EXTERNALLY_VALIDATED) { \t\t\tbool isValidSig; \t\t\tuint256 timestampValidAfter; \t\t\t(signerKey, isValidSig, timestampValidAfter) = validateExternalSig(calls, signature); \t\t\tif (!isValidSig) { \t\t\t\trequire(block.timestamp >= timestampValidAfter, 'SIGNATURE_VALIDATION_TIMELOCK'); \t\t\t\trevert('SIGNATURE_VALIDATION_FAIL'); \t\t\t} \t\t} else { \t\t\tsignerKey = SignatureValidator.recoverAddr( \t\t\t\tkeccak256(abi.encode(address(this), block.chainid, currentNonce, calls)), \t\t\t\tsignature, \t\t\t\ttrue \t\t\t); \t\t\trequire(privileges[signerKey] != bytes32(0), 'INSUFFICIENT_PRIVILEGE'); \t\t} \t\texecuteBatch(calls); \t\t// The actual anti-bricking mechanism - do not allow a signerKey to drop their own privileges \t\trequire(privileges[signerKey] != bytes32(0), 'PRIVILEGE_NOT_DOWNGRADED'); \t} \t/** \t * @notice allows executing multiple bundles of calls (batch together multiple executes) \t * @param toExec an array of execute function parameters \t */ \tfunction executeMultiple(ExecuteArgs[] calldata toExec) external payable { \t\tfor (uint256 i = 0; i != toExec.length; i++) execute(toExec[i].calls, toExec[i].signature); \t} \t/** \t * @notice Allows executing calls if the caller itself is authorized \t * @dev no need for nonce management here cause we're not dealing with sigs \t * @param calls the transaction we're executing \t */ \tfunction executeBySender(Transaction[] calldata calls) external payable { \t\trequire(privileges[msg.sender] != bytes32(0), 'INSUFFICIENT_PRIVILEGE'); \t\texecuteBatch(calls); \t\t// again, anti-bricking \t\trequire(privileges[msg.sender] != bytes32(0), 'PRIVILEGE_NOT_DOWNGRADED'); \t} \t/** \t * @notice allows the contract itself to execute a batch of calls \t * self-calling is useful in cases like wanting to do multiple things in a tryCatchLimit \t * @param calls the calls we're executing \t */ \tfunction executeBySelf(Transaction[] calldata calls) external payable { \t\trequire(msg.sender == address(this), 'ONLY_ACCOUNT_CAN_CALL'); \t\texecuteBatch(calls); \t} \t/** \t * @notice allows the contract itself to execute a single calls \t * self-calling is useful when you want to workaround the executeBatch() \t * protection of not being able to call address(0) \t * @param call the call we're executing \t */ \tfunction executeBySelfSingle(Transaction calldata call) external payable { \t\trequire(msg.sender == address(this), 'ONLY_ACCOUNT_CAN_CALL'); \t\texecuteCall(call.to, call.value, call.data); \t} \t/** \t * @notice Execute a batch of transactions \t * @param calls the transaction we're executing \t */ \tfunction executeBatch(Transaction[] memory calls) internal { \t\tuint256 len = calls.length; \t\tfor (uint256 i = 0; i < len; i++) { \t\t\tTransaction memory call = calls[i]; \t\t\tif (call.to != address(0)) executeCall(call.to, call.value, call.data); \t\t} \t} \t/** \t * @notice Execute a signle transaction \t * @dev we shouldn't use address.call(), cause: https://github.com/ethereum/solidity/issues/2884 \t * @param to the address we're sending to \t * @param value the amount we're sending \t * @param data callData \t */ \tfunction executeCall(address to, uint256 value, bytes memory data) internal { \t\tassembly { \t\t\tlet result := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) \t\t\tif eq(result, 0) { \t\t\t\tlet size := returndatasize() \t\t\t\tlet ptr := mload(0x40) \t\t\t\treturndatacopy(ptr, 0, size) \t\t\t\trevert(ptr, size) \t\t\t} \t\t} \t} \t/** \t * @notice EIP-1271 implementation \t * @dev see https://eips.ethereum.org/EIPS/eip-1271 \t * @param hash the signed hash \t * @param signature the signature for the signed hash \t * @return bytes4 is it a success or a failure \t */ \tfunction isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4) { \t\t(address recovered, bool usedUnprotected) = SignatureValidator.recoverAddrAllowUnprotected(hash, signature, false); \t\tif (uint256(privileges[recovered]) > (usedUnprotected ? 1 : 0)) { \t\t\t// bytes4(keccak256("isValidSignature(bytes32,bytes)") \t\t\treturn 0x1626ba7e; \t\t} else { \t\t\treturn 0xffffffff; \t\t} \t} \t/** \t * @notice EIP-1155 implementation \t * we pretty much only need to signal that we support the interface for 165, but for 1155 we also need the fallback function \t * @param interfaceID the interface we're signaling support for \t * @return bool do we support the interface or not \t */ \tfunction supportsInterface(bytes4 interfaceID) external view returns (bool) { \t\tbool supported = interfaceID == 0x01ffc9a7 || // ERC-165 support (i.e. `bytes4(keccak256('supportsInterface(bytes4)'))`). \t\t\tinterfaceID == 0x150b7a02 || // ERC721TokenReceiver \t\t\tinterfaceID == 0x4e2312e0 || // ERC-1155 `ERC1155TokenReceiver` support (i.e. `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) ^ bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`). \t\t\tinterfaceID == 0x0a417632; // used for checking whether the account is v2 or not \t\tif (supported) return true; \t\taddress payable fallbackHandler = payable(address(uint160(uint256(privileges[FALLBACK_HANDLER_SLOT])))); \t\tif (fallbackHandler == address(0)) return false; \t\treturn AmbireAccount(fallbackHandler).supportsInterface(interfaceID); \t} \t// \t// EIP-4337 implementation \t// \t// return value in case of signature failure, with no time-range. \t// equivalent to packSigTimeRange(true,0,0); \tuint256 constant internal SIG_VALIDATION_FAILED = 1; \t// equivalent to packSigTimeRange(false,0,0); \tuint256 constant internal SIG_VALIDATION_SUCCESS = 0; \t/** \t * @notice EIP-4337 implementation \t * @dev We have an edge case for enabling ERC-4337 in the first if statement. \t * If the function call is to execute, we do not perform an userOp sig validation. \t * We require a one time hash nonce commitment from the paymaster for the given \t * req. We use this to give permissions to the entry point on the fly \t * and enable ERC-4337 \t * @param op the PackedUserOperation we're executing \t * @param userOpHash the hash we've committed to \t * @param missingAccountFunds the funds the account needs to pay \t * @return uint256 0 for success, 1 for signature failure, and a uint256 \t * packed timestamp for a future valid signature: \t * address aggregator, uint48 validUntil, uint48 validAfter \t */ \tfunction validateUserOp(PackedUserOperation calldata op, bytes32 userOpHash, uint256 missingAccountFunds) \texternal payable returns (uint256) \t{ \t\t// enable running executeMultiple operation through the entryPoint if \t\t// a paymaster sponsors it with a commitment one-time nonce. \t\t// two use cases: \t\t// 1) enable 4337 on a network by giving privileges to the entryPoint \t\t// 2) key recovery. If the key is lost, we cannot sign the userOp, \t\t// so we have to go to `execute` to trigger the recovery logic \t\t// Why executeMultiple but not execute? \t\t// executeMultiple allows us to combine recovery + fee payment calls. \t\t// The fee payment call will be with a signature from the new key \t\tif (op.callData.length >= 4 && bytes4(op.callData[0:4]) == this.executeMultiple.selector) { \t\t\t// Require a paymaster, otherwise this mode can be used by anyone to get the user to spend their deposit \t\t\t// @estimation-no-revert \t\t\tif (op.signature.length != 0) return SIG_VALIDATION_FAILED; \t\t\trequire( \t\t\t\top.paymasterAndData.length >= UserOpHelper.PAYMASTER_DATA_OFFSET && \t\t\t\tbytes20(op.paymasterAndData[:UserOpHelper.PAYMASTER_ADDR_OFFSET]) != bytes20(0), \t\t\t\t'validateUserOp: paymaster required in execute() mode' \t\t\t); \t\t\t// hashing in everything except sender (nonces are scoped by sender anyway), nonce, signature \t\t\tuint256 targetNonce = uint256(keccak256( \t\t\t\tabi.encode(op.initCode, op.callData, op.accountGasLimits, op.preVerificationGas, op.gasFees, op.paymasterAndData) \t\t\t)) << 64; \t\t\t// @estimation-no-revert \t\t\tif (op.nonce != targetNonce) return SIG_VALIDATION_FAILED; \t\t\treturn SIG_VALIDATION_SUCCESS; \t\t} \t\trequire(privileges[msg.sender] == ENTRY_POINT_MARKER, 'validateUserOp: not from entryPoint'); \t\t// @estimation \t\t// paying should happen even if signature validation fails \t\tif (missingAccountFunds > 0) { \t\t\t// NOTE: MAY pay more than the minimum, to deposit for future transactions \t\t\t(bool success,) = msg.sender.call{value : missingAccountFunds}(''); \t\t\t// ignore failure (its EntryPoint's job to verify, not account.) \t\t\t(success); \t\t} \t\t// this is replay-safe because userOpHash is retrieved like this: keccak256(abi.encode(userOp.hash(), address(this), block.chainid)) \t\taddress signer = SignatureValidator.recoverAddr(userOpHash, op.signature, true); \t\tif (privileges[signer] == bytes32(0)) return SIG_VALIDATION_FAILED; \t\treturn SIG_VALIDATION_SUCCESS; \t} \tfunction validateExternalSig(Transaction[] memory calls, bytes calldata signature) \tinternal returns(address signerKey, bool isValidSig, uint256 timestampValidAfter) { \t\t(bytes memory sig, ) = SignatureValidator.splitSignature(signature); \t\t// the address of the validator we're using for this validation \t\taddress validatorAddr; \t\t// all the data needed by the validator to execute the validation. \t\t// In the case of DKIMRecoverySigValidator, this is AccInfo: \t\t// abi.encode {string emailFrom; string emailTo; string domainName; \t\t// bytes dkimPubKeyModulus; bytes dkimPubKeyExponent; address secondaryKey; \t\t// bool acceptUnknownSelectors; uint32 waitUntilAcceptAdded; \t\t// uint32 waitUntilAcceptRemoved; bool acceptEmptyDKIMSig; \t\t// bool acceptEmptySecondSig;uint32 onlyOneSigTimelock;} \t\t// The struct is declared in DKIMRecoverySigValidator \t\tbytes memory validatorData; \t\t// the signature data needed by the external validator. \t\t// In the case of DKIMRecoverySigValidator, this is abi.encode( \t\t// SignatureMeta memory sigMeta, bytes memory dkimSig, bytes memory secondSig \t\t// ). \t\tbytes memory innerSig; \t\t// the signerKey in this case is an arbitrary value that does \t\t// not have any specific purpose other than representing \t\t// the privileges key \t\t(signerKey, validatorAddr, validatorData, innerSig) = abi.decode(sig, (address, address, bytes, bytes)); \t\trequire( \t\t\tprivileges[signerKey] == keccak256(abi.encode(validatorAddr, validatorData)), \t\t\t'EXTERNAL_VALIDATION_NOT_SET' \t\t); \t\t// The sig validator itself should throw when a signature isn't validated successfully \t\t// the return value just indicates whether we want to execute the current calls \t\t(isValidSig, timestampValidAfter) = ExternalSigValidator(validatorAddr).validateSig(validatorData, innerSig, calls); \t} } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; import './deployless/IAmbireAccount.sol'; import './libs/Transaction.sol'; /** * @notice A contract used for deploying AmbireAccount.sol * @dev We use create2 to get the AmbireAccount address. It's deterministic: * if the same data is passed to it, the same address will pop out. */ contract AmbireFactory { \tevent LogDeployed(address addr, uint256 salt); \taddress public immutable allowedToDrain; \tconstructor(address allowed) { \t\tallowedToDrain = allowed; \t} \t/** \t * @notice Allows anyone to deploy any contracft with a specific code/salt \t * @dev This is safe because it's CREATE2 deployment \t * @param code the code to be deployed \t * @param salt the salt to shuffle the computed address \t * @return address the deployed address \t */ \tfunction deploy(bytes calldata code, uint256 salt) external returns(address) { \t\treturn deploySafe(code, salt); \t} \t \t/** \t * @notice Call this when you want to deploy the contract and execute calls \t * @dev When the relayer needs to act upon an /identity/:addr/submit call, it'll either call execute on the AmbireAccount directly \t * if it's already deployed, or call `deployAndExecute` if the account is still counterfactual \t * we can't have deployAndExecuteBySender, because the sender will be the factory \t * @param code the code to be deployed \t * @param salt the salt to shuffle the computed address \t * @param txns the txns the are going to be executed \t * @param signature the signature for the txns \t * @return address the deployed address \t */ \tfunction deployAndExecute( \t\tbytes calldata code, \t\tuint256 salt, \t\tTransaction[] calldata txns, \t\tbytes calldata signature \t) external returns (address){ \t\taddress payable addr = payable(deploySafe(code, salt)); \t\tIAmbireAccount(addr).execute(txns, signature); \t\treturn addr; \t} \t \t/** \t * @notice Call this when you want to deploy the contract and call executeMultiple \t * @dev when the relayer needs to act upon an /identity/:addr/submit call, \t * it'll either call execute on the AmbireAccount directly. If it's already \t * deployed, or call `deployAndExecuteMultiple` if the account is still \t * counterfactual but there are multiple accountOps to send \t * @param code the code to be deployed \t * @param salt the salt to shuffle the computed address \t * @param toExec [txns, signature] execute parameters \t * @return address the deployed address \t */ \tfunction deployAndExecuteMultiple( \t\tbytes calldata code, \t\tuint256 salt, \t\tIAmbireAccount.ExecuteArgs[] calldata toExec \t) external returns (address){ \t\taddress payable addr = payable(deploySafe(code, salt)); \t\tIAmbireAccount(addr).executeMultiple(toExec); \t\treturn addr; \t} \t/** \t * @notice This method can be used to withdraw stuck tokens or airdrops \t * @dev Only allowedToDrain can do the call \t * @param to receiver \t * @param value how much to be sent \t * @param data if a token has airdropped, code to send it \t * @param gas maximum gas willing to spend \t */ \tfunction call(address to, uint256 value, bytes calldata data, uint256 gas) external { \t\trequire(msg.sender == allowedToDrain, 'ONLY_AUTHORIZED'); \t\t(bool success, bytes memory err) = to.call{ gas: gas, value: value }(data); \t\trequire(success, string(err)); \t} \t \t/** \t * @dev This is done to mitigate possible frontruns where, for example, \t * where deploying the same code/salt via deploy() would make a pending \t * deployAndExecute fail. The way we mitigate that is by checking if the \t * contract is already deployed and if so, we continue execution \t * @param code the code to be deployed \t * @param salt the salt to shuffle the computed address \t * @return address the deployed address \t */ \tfunction deploySafe(bytes memory code, uint256 salt) internal returns (address) { \t\taddress expectedAddr = address( \t\t\tuint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(code))))) \t\t); \t\tuint256 size; \t\tassembly { \t\t\tsize := extcodesize(expectedAddr) \t\t} \t\t// If there is code at that address, we can assume it's the one we were about to deploy, \t\t// because of how CREATE2 and keccak256 works \t\tif (size == 0) { \t\t\taddress addr; \t\t\tassembly { \t\t\t\taddr := create2(0, add(code, 0x20), mload(code), salt) \t\t\t} \t\t\trequire(addr != address(0), 'FAILED_DEPLOYING'); \t\t\trequire(addr == expectedAddr, 'FAILED_MATCH'); \t\t\temit LogDeployed(addr, salt); \t\t} \t\treturn expectedAddr; \t} } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; import './deployless/IAmbireAccount.sol'; import './libs/erc4337/IPaymaster.sol'; import './libs/SignatureValidator.sol'; import './libs/erc4337/UserOpHelper.sol'; contract AmbirePaymaster is IPaymaster { \taddress immutable public relayer; \tconstructor(address _relayer) { \t\trelayer = _relayer; \t} \t/** \t * @notice This method can be used to withdraw stuck tokens or airdrops \t * \t * @param to The address we're calling \t * @param value The value in the call \t * @param\tdata\tthe call data \t * @param\tgas\tthe call gas \t */ \tfunction call(address to, uint256 value, bytes calldata data, uint256 gas) external payable { \t\trequire(msg.sender == relayer, 'call: not relayer'); \t\t(bool success, bytes memory err) = to.call{ gas: gas, value: value }(data); \t\trequire(success, string(err)); \t} \t/** \t * @notice Validate user operations the paymaster has signed \t * We do not need to send funds to the EntryPoint because we rely on pre-existing deposit. \t * Requests are chain specific to prevent signature reuse. \t * @dev We have two use cases for the paymaster: \t * - normal erc-4337. Everything is per ERC-4337 standard, the nonce is sequential. \t * - an executeMultiple call. If the calldata is executeMultiple, we've hardcoded \t * a 0 nonce. That's what's called a one-time hash nonce and its key is actually \t * the commitment. Check EntryPoint -> NonceManager for more information. \t * \t * @param userOp the UserOperation we're executing \t * @return context context is returned in the postOp and called by the \t * EntryPoint. But we're not using postOp is context is always emtpy \t * @return validationData This consists of: \t * - an aggregator address: address(uint160(validationData)). This is used \t * when you want an outer contract to determine whether the signature is valid. \t * In our case, this is always 0 (address 0) for valid signatures and \t * 1 (address 1) for invalid. This is what the entry point expects and \t * in those two cases, an outer contract is obviously not called. \t * - a uint48 validUntil: uint48(validationData >> 160) \t * A Paymaster signature can be signed at time "x" but delayed intentionally \t * until time "y" when a fee payment's price has dropped significantly or \t * some other issue. validUntil sets a time validity for the signature * - a uint48 validAfter: uint48(validationData >> (48 + 160)) \t * If the signature should be valid only after a period of time, \t * we tweak the validAfter property. \t * For more information, check EntryPoint -> _getValidationData() \t */ \tfunction validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32, uint256) \t\texternal \t\tview \t\treturns (bytes memory context, uint256 validationData) \t{ \t\t(uint48 validUntil, uint48 validAfter, bytes memory signature) = abi.decode( \t\t\tuserOp.paymasterAndData[UserOpHelper.PAYMASTER_DATA_OFFSET:], \t\t\t(uint48, uint48, bytes) \t\t); \t\tbytes memory callData = userOp.callData; \t\tbytes32 hash = keccak256(abi.encode( \t\t\tblock.chainid, \t\t\taddress(this), \t\t\t// entry point \t\t\tmsg.sender, \t\t\tvalidUntil, \t\t\tvalidAfter, \t\t\t// everything except paymasterAndData and signature \t\t\tuserOp.sender, \t\t\t// for the nonce we have an exception case: one-time nonces depend on paymasterAndData, which is generated by the relayer \t\t\t// we can't have this as part of the sig cuz we create a cyclical dep \t\t\t// the nonce can only be used once, so one cannot replay the gas payment \t\t\tcallData.length >= 4 && bytes4(userOp.callData[0:4]) == IAmbireAccount.executeMultiple.selector ? 0 : userOp.nonce, \t\t\tuserOp.initCode, \t\t\tcallData, \t\t\tuserOp.accountGasLimits, \t\t\tuserOp.preVerificationGas, \t\t\tuserOp.gasFees \t\t)); \t\t(address recovered, ) = SignatureValidator.recoverAddrAllowUnprotected(hash, signature, true); \t\tbool isValidSig = recovered == relayer; \t\t// see _packValidationData: https://github.com/eth-infinitism/account-abstraction/blob/f2b09e60a92d5b3177c68d9f382912ccac19e8db/contracts/core/Helpers.sol#L73-L80 \t\treturn ("", uint160(isValidSig ? 0 : 1) | (uint256(validUntil) << 160) | (uint256(validAfter) << 208)); \t} \t/** \t * @notice No-op, won't be used because we don't return a context \t * @param mode . \t * @param context . \t * @param actualGasCost . \t */ \tfunction postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) external { \t\t// No-op, won't be used because we don't return a context \t} } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; import './libs/Transaction.sol'; /** * @title ExternalSigValidator * @notice A way to add custom recovery to AmbireAccount. * address accountAddr is the Ambire account address * bytes calldata data is all the data needed by the ExternalSigValidator. * It could be anything and it's validator specific. * bytes calldata sig is the signature we're validating. Notice its not * bytes32 so there could be cases where its not only the signature. It's * validator specific * uint256 nonce - the Ambire account nonce * Transaction[] calldata calls - the txns that are going to be executed * if the validation is successful * @dev Not all passed properties necessarily need to be used. */ abstract contract ExternalSigValidator { \tfunction validateSig( \t\tbytes calldata data, \t\tbytes calldata sig, \t\tTransaction[] calldata calls \t) external virtual returns (bool isValidSignature, uint256 timestampValidAfter); }// SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.8.7; import '../libs/Transaction.sol'; interface IAmbireAccount { \tfunction privileges(address addr) external returns (bytes32); \tfunction nonce() external returns (uint); \tstruct RecoveryInfo { \t\taddress[] keys; \t\tuint timelock; \t} \tstruct ExecuteArgs { \t\tTransaction[] calls; \t\tbytes signature; \t} \tfunction setAddrPrivilege(address addr, bytes32 priv) external payable; \tfunction tryCatch(address to, uint value, bytes calldata data) external payable; \tfunction tryCatchLimit(address to, uint value, bytes calldata data, uint gasLimit) external payable; \tfunction execute(Transaction[] calldata txns, bytes calldata signature) external payable; \tfunction executeBySender(Transaction[] calldata txns) external payable; \tfunction executeBySelf(Transaction[] calldata txns) external payable; \tfunction executeMultiple(ExecuteArgs[] calldata toExec) external payable; \t// EIP 1271 implementation \t// see https://eips.ethereum.org/EIPS/eip-1271 \tfunction isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4); \tfunction supportsInterface(bytes4 interfaceID) external view returns (bool); } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; library Bytes { \tfunction trimToSize(bytes memory b, uint256 newLen) internal pure { \t\trequire(b.length > newLen, 'BytesLib: only shrinking'); \t\tassembly { \t\t\tmstore(b, newLen) \t\t} \t} \t/***********************************| \t| Read Bytes Functions | \t|__________________________________*/ \t/** \t * @dev Reads a bytes32 value from a position in a byte array. \t * @param b Byte array containing a bytes32 value. \t * @param index Index in byte array of bytes32 value. \t * @return result bytes32 value from byte array. \t */ \tfunction readBytes32(bytes memory b, uint256 index) internal pure returns (bytes32 result) { \t\t// Arrays are prefixed by a 256 bit length parameter \t\tindex += 32; \t\trequire(b.length >= index, 'BytesLib: length'); \t\t// Read the bytes32 from array memory \t\tassembly { \t\t\tresult := mload(add(b, index)) \t\t} \t\treturn result; \t} } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; import './Bytes.sol'; interface IERC1271Wallet { \tfunction isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue); } library SignatureValidator { \tusing Bytes for bytes; \tenum SignatureMode { \t\t// the first mode Unprotected is used in combination with EIP-1271 signature verification to do \t\t// EIP-712 verifications, as well as "Ethereum signed message:" message verifications \t\t// The caveat with this is that we need to ensure that the signer key used for it isn't reused, or the message body \t\t// itself contains context about the wallet (such as it's address) \t\t// We do this, rather than applying the prefix on-chain, because if we do you won't be able to see the message \t\t// when signing on a hardware wallet (you'll only see the hash) - since `isValidSignature` can only receive the hash - \t\t// if the prefix is applied on-chain you can never match it - it's hash(prefix+hash(msg)) vs hash(prefix+msg) \t\t// As for transactions (`execute()`), those can be signed with any of the modes \t\t// Otherwise, if it's reused, we MUST use `Standard` mode which always wraps the final digest hash, but unfortnately this means \t\t// you can't preview the full message when signing on a HW wallet \t\tUnprotected, \t\tStandard, \t\tSmartWallet, \t\tSpoof, \t\tSchnorr, \t\tMultisig, \t\t// WARNING: Signature modes should not be more than 26 as the "v" \t\t// value for standard ecrecover is 27/28 \t\t// WARNING: must always be last \t\tLastUnused \t} \t// bytes4(keccak256("isValidSignature(bytes32,bytes)")) \tbytes4 internal constant ERC1271_MAGICVALUE_BYTES32 = 0x1626ba7e; \t// secp256k1 group order \tuint256 internal constant Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; \tfunction splitSignature(bytes memory sig) internal pure returns (bytes memory, uint8) { \t\tuint8 modeRaw; \t\tunchecked { \t\t\tmodeRaw = uint8(sig[sig.length - 1]); \t\t} \t\tsig.trimToSize(sig.length - 1); \t\treturn (sig, modeRaw); \t} \tfunction recoverAddr(bytes32 hash, bytes memory sig, bool allowSpoofing) internal view returns (address) { \t\t(address recovered, bool usedUnprotected) = recoverAddrAllowUnprotected(hash, sig, allowSpoofing); \t\trequire(!usedUnprotected, 'SV_USED_UNBOUND'); \t\treturn recovered; \t} \tfunction recoverAddrAllowUnprotected(bytes32 hash, bytes memory sig, bool allowSpoofing) internal view returns (address, bool) { \t\trequire(sig.length != 0, 'SV_SIGLEN'); \t\tuint8 modeRaw; \t\tunchecked { \t\t\tmodeRaw = uint8(sig[sig.length - 1]); \t\t} \t\t// Ensure we're in bounds for mode; Solidity does this as well but it will just silently blow up rather than showing a decent error \t\tif (modeRaw >= uint8(SignatureMode.LastUnused)) { \t\t\tif (sig.length == 65) modeRaw = uint8(SignatureMode.Unprotected); \t\t\telse revert('SV_SIGMODE'); \t\t} \t\tSignatureMode mode = SignatureMode(modeRaw); \t\t// the address of the key we are gonna be returning \t\taddress signerKey; \t\t// wrap in the EIP712 wrapping if it's not unbound \t\t// multisig gets an exception because each inner sig will have to apply this logic \t\t// @TODO should spoofing be removed from this? \t\tbool isUnprotected = mode == SignatureMode.Unprotected || mode == SignatureMode.Multisig; \t\tif (!isUnprotected) { \t\t\tbytes32 DOMAIN_SEPARATOR = keccak256(abi.encode( \t\t\t\tkeccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)'), \t\t\t\tkeccak256(bytes('Ambire')), \t\t\t\tkeccak256(bytes('1')), \t\t\t\tblock.chainid, \t\t\t\taddress(this), \t\t\t\tbytes32(0) \t\t\t)); \t\t\thash = keccak256(abi.encodePacked( \t\t\t\t'\\x19\\x01', \t\t\t\tDOMAIN_SEPARATOR, \t\t\t\tkeccak256(abi.encode( \t\t\t\t\tkeccak256(bytes('AmbireOperation(address account,bytes32 hash)')), \t\t\t\t\taddress(this), \t\t\t\t\thash \t\t\t\t)) \t\t\t)); \t\t} \t\t// {r}{s}{v}{mode} \t\tif (mode == SignatureMode.Unprotected || mode == SignatureMode.Standard) { \t\t\trequire(sig.length == 65 || sig.length == 66, 'SV_LEN'); \t\t\tbytes32 r = sig.readBytes32(0); \t\t\tbytes32 s = sig.readBytes32(32); \t\t\tuint8 v = uint8(sig[64]); \t\t\tsignerKey = ecrecover(hash, v, r, s); \t\t// {sig}{verifier}{mode} \t\t} else if (mode == SignatureMode.Schnorr) { \t\t\t// Based on https://hackmd.io/@nZ-twauPRISEa6G9zg3XRw/SyjJzSLt9 \t\t\t// You can use this library to produce signatures: https://github.com/borislav-itskov/schnorrkel.js \t\t\t// px := public key x-coord \t\t\t// e := schnorr signature challenge \t\t\t// s := schnorr signature \t\t\t// parity := public key y-coord parity (27 or 28) \t\t\t// last uint8 is for the Ambire sig mode - it's ignored \t\t\tsig.trimToSize(sig.length - 1); \t\t\t(bytes32 px, bytes32 e, bytes32 s, uint8 parity) = abi.decode(sig, (bytes32, bytes32, bytes32, uint8)); \t\t\t// ecrecover = (m, v, r, s); \t\t\tbytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q)); \t\t\tbytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q)); \t\t\trequire(sp != bytes32(Q)); \t\t\t// the ecrecover precompile implementation checks that the `r` and `s` \t\t\t// inputs are non-zero (in this case, `px` and `ep`), thus we don't need to \t\t\t// check if they're zero. \t\t\taddress R = ecrecover(sp, parity, px, ep); \t\t\trequire(R != address(0), 'SV_ZERO_SIG'); \t\t\trequire(e == keccak256(abi.encodePacked(R, uint8(parity), px, hash)), 'SV_SCHNORR_FAILED'); \t\t\tsignerKey = address(uint160(uint256(keccak256(abi.encodePacked('SCHNORR', px))))); \t\t} else if (mode == SignatureMode.Multisig) { \t\t\tsig.trimToSize(sig.length - 1); \t\t\tbytes[] memory signatures = abi.decode(sig, (bytes[])); \t\t\t// since we're in a multisig, we care if any of the inner sigs are unbound \t\t\tisUnprotected = false; \t\t\tfor (uint256 i = 0; i != signatures.length; i++) { \t\t\t\t(address inner, bool isInnerUnprotected) = recoverAddrAllowUnprotected(hash, signatures[i], false); \t\t\t\tif (isInnerUnprotected) isUnprotected = true; \t\t\t\tsignerKey = address( \t\t\t\t\tuint160(uint256(keccak256(abi.encodePacked(signerKey, inner)))) \t\t\t\t); \t\t\t} \t\t} else if (mode == SignatureMode.SmartWallet) { \t\t\t// 32 bytes for the addr, 1 byte for the type = 33 \t\t\trequire(sig.length > 33, 'SV_LEN_WALLET'); \t\t\tuint256 newLen; \t\t\tunchecked { \t\t\t\tnewLen = sig.length - 33; \t\t\t} \t\t\tIERC1271Wallet wallet = IERC1271Wallet(address(uint160(uint256(sig.readBytes32(newLen))))); \t\t\tsig.trimToSize(newLen); \t\t\trequire(ERC1271_MAGICVALUE_BYTES32 == wallet.isValidSignature(hash, sig), 'SV_WALLET_INVALID'); \t\t\tsignerKey = address(wallet); \t\t// {address}{mode}; the spoof mode is used when simulating calls \t\t} else if (mode == SignatureMode.Spoof && allowSpoofing) { \t\t\t// This is safe cause it's specifically intended for spoofing sigs in simulation conditions, where tx.origin can be controlled \t\t\t// We did not choose 0x00..00 because in future network upgrades tx.origin may be nerfed or there may be edge cases in which \t\t\t// it is zero, such as native account abstraction \t\t\t// slither-disable-next-line tx-origin \t\t\trequire(tx.origin == address(1) || tx.origin == address(6969), 'SV_SPOOF_ORIGIN'); \t\t\trequire(sig.length == 33, 'SV_SPOOF_LEN'); \t\t\tsig.trimToSize(32); \t\t\t// To simulate the gas usage; check is just to silence unused warning \t\t\trequire(ecrecover(0, 0, 0, 0) != address(6969)); \t\t\tsignerKey = abi.decode(sig, (address)); \t\t} else { \t\t\trevert('SV_TYPE'); \t\t} \t\trequire(signerKey != address(0), 'SV_ZERO_SIG'); \t\treturn (signerKey, isUnprotected); \t} } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; // Transaction structure // we handle replay protection separately by requiring (address(this), chainID, nonce) as part of the sig // @dev a better name for this would be `Call`, but we are keeping `Transaction` for backwards compatibility struct Transaction { address to; uint256 value; bytes data; } // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.12; import "./PackedUserOperation.sol"; /** * the interface exposed by a paymaster contract, who agrees to pay the gas for user's operations. * a paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. */ interface IPaymaster { enum PostOpMode { opSucceeded, // user op succeeded opReverted, // user op reverted. still has to pay for gas. postOpReverted //user op succeeded, but caused postOp to revert. Now it's a 2nd call, after user's op was deliberately reverted. } /** * payment validation: check if paymaster agrees to pay. * Must verify sender is the entryPoint. * Revert to reject this request. * Note that bundlers will reject this method if it changes the state, unless the paymaster is trusted (whitelisted) * The paymaster pre-pays using its deposit, and receive back a refund after the postOp method returns. * @param userOp the user operation * @param userOpHash hash of the user's request data. * @param maxCost the maximum cost of this transaction (based on maximum gas and gas price from userOp) * @return context value to send to a postOp * zero length to signify postOp is not required. * @return validationData signature and time-range of this operation, encoded the same as the return value of validateUserOperation * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, * otherwise, an address of an "authorizer" contract. * <6-byte> validUntil - last timestamp this operation is valid. 0 for "indefinite" * <6-byte> validAfter - first timestamp this operation is valid * Note that the validation code cannot use block.timestamp (or block.number) directly. */ function validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost) external returns (bytes memory context, uint256 validationData); /** * post-operation handler. * Must verify sender is the entryPoint * @param mode enum with the following options: * opSucceeded - user operation succeeded. * opReverted - user op reverted. still has to pay for gas. * postOpReverted - user op succeeded, but caused postOp (in mode=opSucceeded) to revert. * Now this is the 2nd call, after user's op was deliberately reverted. * @param context - the context value returned by validatePaymasterUserOp * @param actualGasCost - actual gas used so far (without this postOp call). */ function postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) external; } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; /** * User Operation struct * @param sender - The sender account of this request. * @param nonce - Unique value the sender uses to verify it is not a replay. * @param initCode - If set, the account contract will be created by this constructor/ * @param callData - The method call to execute on this account. * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. * Covers batch overhead. * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data * The paymaster will pay for the transaction instead of the sender. * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. */ struct PackedUserOperation { address sender; uint256 nonce; bytes initCode; bytes callData; // callGasLimit + verificationGasLimit bytes32 accountGasLimits; uint256 preVerificationGas; // maxFeePerGas + maxPriorityFeePerGas bytes32 gasFees; bytes paymasterAndData; bytes signature; } // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.19; library UserOpHelper { \tuint256 public constant PAYMASTER_ADDR_OFFSET = 20; // 52 = 20 address + 16 paymasterVerificationGasLimit + 16 paymasterPostOpGasLimit \tuint256 public constant PAYMASTER_DATA_OFFSET = 52; }