Transaction Hash:
Block:
23174050 at Aug-19-2025 08:53:35 AM +UTC
Transaction Fee:
0.000053673230867698 ETH
$0.10
Gas Used:
135,298 Gas / 0.396703801 Gwei
Emitted Events:
| 596 |
EntryPoint.BeforeExecution( )
|
| 597 |
TetherToken.Transfer( from=0xD8293ad21678c6F09Da139b4B62D38e514a03B78, to=0xE691cDAbc7dC1CD2138e21F75caF0bd048696133, value=100 )
|
| 598 |
EntryPoint.UserOperationEvent( userOpHash=36A9E0333351B1D5B7DCE8F11C1EE7A863AD44E2879C787E06B614E20F60797F, sender=0xD8293ad21678c6F09Da139b4B62D38e514a03B78, paymaster=AmbirePaymaster, nonce=110, success=True, actualGasCost=53859681654168, actualGasUsed=135768 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x00000000...6f37da032 | (Entry Point 0.7.0) | 114.5950510443562213 Eth | 114.594997184674567132 Eth | 0.000053859681654168 | |
|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 20.012680327551356434 Eth | 20.012680330516817998 Eth | 0.000000002965461564 | |
| 0x77162EB0...B451a5E7B | (Bundler: 0x771...e7b) |
0.143542604519329037 Eth
Nonce: 150
|
0.143542790970115507 Eth
Nonce: 151
| 0.00000018645078647 | |
| 0xdAC17F95...13D831ec7 |
Execution Trace
EntryPoint.handleOps( ops=, beneficiary=0x77162EB08A150E126C20E6E8D0601a3B451a5E7B )
0xd8293ad21678c6f09da139b4b62d38e514a03b78.19822f7c( )-
Null: 0x000...001.dac86242( )
-
AmbirePaymaster.validatePaymasterUserOp( userOp=[{name:sender, type:address, order:1, indexed:false, value:0xD8293ad21678c6F09Da139b4B62D38e514a03B78, valueString:0xD8293ad21678c6F09Da139b4B62D38e514a03B78}, {name:nonce, type:uint256, order:2, indexed:false, value:110, valueString:110}, {name:initCode, type:bytes, order:3, indexed:false, value:0x, valueString:0x}, {name:callData, type:bytes, order:4, indexed:false, value:0xABC5345E0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000DAC17F958D2EE523A2206206994597C13D831EC7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044A9059CBB000000000000000000000000E691CDABC7DC1CD2138E21F75CAF0BD048696133000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000942F9CE5D9A33A82F88D233AEB3292E6802303480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000E000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000036AB400000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000000000000000000000000000000000000000000767617354616E6B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000, valueString:0xABC5345E0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000DAC17F958D2EE523A2206206994597C13D831EC7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044A9059CBB000000000000000000000000E691CDABC7DC1CD2138E21F75CAF0BD048696133000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000942F9CE5D9A33A82F88D233AEB3292E6802303480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000E000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000036AB400000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000000000000000000000000000000000000000000767617354616E6B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000}, {name:accountGasLimits, type:bytes32, order:5, indexed:false, value:0000000000000000000000000000C9F20000000000000000000000000000B814, valueString:0000000000000000000000000000C9F20000000000000000000000000000B814}, {name:preVerificationGas, type:uint256, order:6, indexed:false, value:50217, valueString:50217}, {name:gasFees, type:bytes32, order:7, indexed:false, value:0000000000000000000000000000559E0000000000000000000000001C0E87F6, valueString:0000000000000000000000000000559E0000000000000000000000001C0E87F6}, {name:paymasterAndData, type:bytes, order:8, indexed:false, value:0xA8B267C68715FA1DCA055993149F30217B572CF00000000000000000000000000000A410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068A43C2700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004298C3BADA633F0309EDC3BF279BC6B647D32E3677654A910B0102C986AF47DAA1679BDBC7C56B4DE09F985E29A93A275C75C25E39097DB698511737B50B37AE741B00000000000000000000000000000000000000000000000000000000000000, valueString:0xA8B267C68715FA1DCA055993149F30217B572CF00000000000000000000000000000A410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068A43C2700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004298C3BADA633F0309EDC3BF279BC6B647D32E3677654A910B0102C986AF47DAA1679BDBC7C56B4DE09F985E29A93A275C75C25E39097DB698511737B50B37AE741B00000000000000000000000000000000000000000000000000000000000000}, {name:signature, type:bytes, order:9, indexed:false, value:0xF990E56257441D3019C14F6002826F9A92D2E307E814737E94ECFE714DFBB7F823233274BFF438E831342FF8DCBFC8B7926D2A922FE7F0EF22A28BD655BB4BDB1B00, valueString:0xF990E56257441D3019C14F6002826F9A92D2E307E814737E94ECFE714DFBB7F823233274BFF438E831342FF8DCBFC8B7926D2A922FE7F0EF22A28BD655BB4BDB1B00}], 36A9E0333351B1D5B7DCE8F11C1EE7A863AD44E2879C787E06B614E20F60797F, 89924800237962 ) => ( context=0x, validationData=2565803164958427679680499928749144148715893657021125230592 )-
Null: 0x000...001.fd415890( )
-
EntryPoint.innerHandleOp( callData=0xABC5345E0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000DAC17F958D2EE523A2206206994597C13D831EC7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044A9059CBB000000000000000000000000E691CDABC7DC1CD2138E21F75CAF0BD048696133000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000942F9CE5D9A33A82F88D233AEB3292E6802303480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000E000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000036AB400000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000000000000000000000000000000000000000000767617354616E6B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000, opInfo=[{name:mUserOp, type:tuple, order:1, indexed:false, value:[{name:sender, type:address, order:1, indexed:false, value:0xD8293ad21678c6F09Da139b4B62D38e514a03B78, valueString:0xD8293ad21678c6F09Da139b4B62D38e514a03B78}, {name:nonce, type:uint256, order:2, indexed:false, value:110, valueString:110}, {name:verificationGasLimit, type:uint256, order:3, indexed:false, value:51698, valueString:51698}, {name:callGasLimit, type:uint256, order:4, indexed:false, value:47124, valueString:47124}, {name:paymasterVerificationGasLimit, type:uint256, order:5, indexed:false, value:42000, valueString:42000}, {name:paymasterPostOpGasLimit, type:uint256, order:6, indexed:false, value:0, valueString:0}, {name:preVerificationGas, type:uint256, order:7, indexed:false, value:50217, valueString:50217}, {name:paymaster, type:address, order:8, indexed:false, value:0xA8B267C68715FA1Dca055993149f30217B572Cf0, valueString:0xA8B267C68715FA1Dca055993149f30217B572Cf0}, {name:maxFeePerGas, type:uint256, order:9, indexed:false, value:470714358, valueString:470714358}, {name:maxPriorityFeePerGas, type:uint256, order:10, indexed:false, value:21918, valueString:21918}], valueString:[{name:sender, type:address, order:1, indexed:false, value:0xD8293ad21678c6F09Da139b4B62D38e514a03B78, valueString:0xD8293ad21678c6F09Da139b4B62D38e514a03B78}, {name:nonce, type:uint256, order:2, indexed:false, value:110, valueString:110}, {name:verificationGasLimit, type:uint256, order:3, indexed:false, value:51698, valueString:51698}, {name:callGasLimit, type:uint256, order:4, indexed:false, value:47124, valueString:47124}, {name:paymasterVerificationGasLimit, type:uint256, order:5, indexed:false, value:42000, valueString:42000}, {name:paymasterPostOpGasLimit, type:uint256, order:6, indexed:false, value:0, valueString:0}, {name:preVerificationGas, type:uint256, order:7, indexed:false, value:50217, valueString:50217}, {name:paymaster, type:address, order:8, indexed:false, value:0xA8B267C68715FA1Dca055993149f30217B572Cf0, valueString:0xA8B267C68715FA1Dca055993149f30217B572Cf0}, {name:maxFeePerGas, type:uint256, order:9, indexed:false, value:470714358, valueString:470714358}, {name:maxPriorityFeePerGas, type:uint256, order:10, indexed:false, value:21918, valueString:21918}]}, {name:userOpHash, type:bytes32, order:2, indexed:false, value:36A9E0333351B1D5B7DCE8F11C1EE7A863AD44E2879C787E06B614E20F60797F, valueString:36A9E0333351B1D5B7DCE8F11C1EE7A863AD44E2879C787E06B614E20F60797F}, {name:prefund, type:uint256, order:3, indexed:false, value:89924800237962, valueString:89924800237962}, {name:contextOffset, type:uint256, order:4, indexed:false, value:1216, valueString:1216}, {name:preOpGas, type:uint256, order:5, indexed:false, value:100146, valueString:100146}], context=0x ) => ( actualGasCost=53859681654168 )0xd8293ad21678c6f09da139b4b62d38e514a03b78.abc5345e( )-
TetherToken.transfer( _to=0xE691cDAbc7dC1CD2138e21F75caF0bd048696133, _value=100 )
-
Ambire Wallet: Deployer.00000000( )
-
- ETH 0.000053859681654168
Bundler: 0x771...e7b.CALL( )
File 1 of 3: EntryPoint
File 2 of 3: TetherToken
File 3 of 3: AmbirePaymaster
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
import "../interfaces/IAccount.sol";
import "../interfaces/IAccountExecute.sol";
import "../interfaces/IPaymaster.sol";
import "../interfaces/IEntryPoint.sol";
import "../utils/Exec.sol";
import "./StakeManager.sol";
import "./SenderCreator.sol";
import "./Helpers.sol";
import "./NonceManager.sol";
import "./UserOperationLib.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/*
* Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
* Only one instance required on each chain.
*/
/// @custom:security-contact https://bounty.ethereum.org
contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, ERC165 {
using UserOperationLib for PackedUserOperation;
SenderCreator private immutable _senderCreator = new SenderCreator();
function senderCreator() internal view virtual returns (SenderCreator) {
return _senderCreator;
}
//compensate for innerHandleOps' emit message and deposit refund.
// allow some slack for future gas price changes.
uint256 private constant INNER_GAS_OVERHEAD = 10000;
// Marker for inner call revert on out of gas
bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead";
bytes32 private constant INNER_REVERT_LOW_PREFUND = hex"deadaa51";
uint256 private constant REVERT_REASON_MAX_LEN = 2048;
uint256 private constant PENALTY_PERCENT = 10;
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
// note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything
return interfaceId == (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId) ||
interfaceId == type(IEntryPoint).interfaceId ||
interfaceId == type(IStakeManager).interfaceId ||
interfaceId == type(INonceManager).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* Compensate the caller's beneficiary address with the collected fees of all UserOperations.
* @param beneficiary - The address to receive the fees.
* @param amount - Amount to transfer.
*/
function _compensate(address payable beneficiary, uint256 amount) internal {
require(beneficiary != address(0), "AA90 invalid beneficiary");
(bool success, ) = beneficiary.call{value: amount}("");
require(success, "AA91 failed send to beneficiary");
}
/**
* Execute a user operation.
* @param opIndex - Index into the opInfo array.
* @param userOp - The userOp to execute.
* @param opInfo - The opInfo filled by validatePrepayment for this userOp.
* @return collected - The total amount this userOp paid.
*/
function _executeUserOp(
uint256 opIndex,
PackedUserOperation calldata userOp,
UserOpInfo memory opInfo
)
internal
returns
(uint256 collected) {
uint256 preGas = gasleft();
bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset);
bool success;
{
uint256 saveFreePtr;
assembly ("memory-safe") {
saveFreePtr := mload(0x40)
}
bytes calldata callData = userOp.callData;
bytes memory innerCall;
bytes4 methodSig;
assembly {
let len := callData.length
if gt(len, 3) {
methodSig := calldataload(callData.offset)
}
}
if (methodSig == IAccountExecute.executeUserOp.selector) {
bytes memory executeUserOp = abi.encodeCall(IAccountExecute.executeUserOp, (userOp, opInfo.userOpHash));
innerCall = abi.encodeCall(this.innerHandleOp, (executeUserOp, opInfo, context));
} else
{
innerCall = abi.encodeCall(this.innerHandleOp, (callData, opInfo, context));
}
assembly ("memory-safe") {
success := call(gas(), address(), 0, add(innerCall, 0x20), mload(innerCall), 0, 32)
collected := mload(0)
mstore(0x40, saveFreePtr)
}
}
if (!success) {
bytes32 innerRevertCode;
assembly ("memory-safe") {
let len := returndatasize()
if eq(32,len) {
returndatacopy(0, 0, 32)
innerRevertCode := mload(0)
}
}
if (innerRevertCode == INNER_OUT_OF_GAS) {
// handleOps was called with gas limit too low. abort entire bundle.
//can only be caused by bundler (leaving not enough gas for inner call)
revert FailedOp(opIndex, "AA95 out of gas");
} else if (innerRevertCode == INNER_REVERT_LOW_PREFUND) {
// innerCall reverted on prefund too low. treat entire prefund as "gas cost"
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
uint256 actualGasCost = opInfo.prefund;
emitPrefundTooLow(opInfo);
emitUserOperationEvent(opInfo, false, actualGasCost, actualGas);
collected = actualGasCost;
} else {
emit PostOpRevertReason(
opInfo.userOpHash,
opInfo.mUserOp.sender,
opInfo.mUserOp.nonce,
Exec.getReturnData(REVERT_REASON_MAX_LEN)
);
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
collected = _postExecution(
IPaymaster.PostOpMode.postOpReverted,
opInfo,
context,
actualGas
);
}
}
}
function emitUserOperationEvent(UserOpInfo memory opInfo, bool success, uint256 actualGasCost, uint256 actualGas) internal virtual {
emit UserOperationEvent(
opInfo.userOpHash,
opInfo.mUserOp.sender,
opInfo.mUserOp.paymaster,
opInfo.mUserOp.nonce,
success,
actualGasCost,
actualGas
);
}
function emitPrefundTooLow(UserOpInfo memory opInfo) internal virtual {
emit UserOperationPrefundTooLow(
opInfo.userOpHash,
opInfo.mUserOp.sender,
opInfo.mUserOp.nonce
);
}
/// @inheritdoc IEntryPoint
function handleOps(
PackedUserOperation[] calldata ops,
address payable beneficiary
) public nonReentrant {
uint256 opslen = ops.length;
UserOpInfo[] memory opInfos = new UserOpInfo[](opslen);
unchecked {
for (uint256 i = 0; i < opslen; i++) {
UserOpInfo memory opInfo = opInfos[i];
(
uint256 validationData,
uint256 pmValidationData
) = _validatePrepayment(i, ops[i], opInfo);
_validateAccountAndPaymasterValidationData(
i,
validationData,
pmValidationData,
address(0)
);
}
uint256 collected = 0;
emit BeforeExecution();
for (uint256 i = 0; i < opslen; i++) {
collected += _executeUserOp(i, ops[i], opInfos[i]);
}
_compensate(beneficiary, collected);
}
}
/// @inheritdoc IEntryPoint
function handleAggregatedOps(
UserOpsPerAggregator[] calldata opsPerAggregator,
address payable beneficiary
) public nonReentrant {
uint256 opasLen = opsPerAggregator.length;
uint256 totalOps = 0;
for (uint256 i = 0; i < opasLen; i++) {
UserOpsPerAggregator calldata opa = opsPerAggregator[i];
PackedUserOperation[] calldata ops = opa.userOps;
IAggregator aggregator = opa.aggregator;
//address(1) is special marker of "signature error"
require(
address(aggregator) != address(1),
"AA96 invalid aggregator"
);
if (address(aggregator) != address(0)) {
// solhint-disable-next-line no-empty-blocks
try aggregator.validateSignatures(ops, opa.signature) {} catch {
revert SignatureValidationFailed(address(aggregator));
}
}
totalOps += ops.length;
}
UserOpInfo[] memory opInfos = new UserOpInfo[](totalOps);
uint256 opIndex = 0;
for (uint256 a = 0; a < opasLen; a++) {
UserOpsPerAggregator calldata opa = opsPerAggregator[a];
PackedUserOperation[] calldata ops = opa.userOps;
IAggregator aggregator = opa.aggregator;
uint256 opslen = ops.length;
for (uint256 i = 0; i < opslen; i++) {
UserOpInfo memory opInfo = opInfos[opIndex];
(
uint256 validationData,
uint256 paymasterValidationData
) = _validatePrepayment(opIndex, ops[i], opInfo);
_validateAccountAndPaymasterValidationData(
i,
validationData,
paymasterValidationData,
address(aggregator)
);
opIndex++;
}
}
emit BeforeExecution();
uint256 collected = 0;
opIndex = 0;
for (uint256 a = 0; a < opasLen; a++) {
UserOpsPerAggregator calldata opa = opsPerAggregator[a];
emit SignatureAggregatorChanged(address(opa.aggregator));
PackedUserOperation[] calldata ops = opa.userOps;
uint256 opslen = ops.length;
for (uint256 i = 0; i < opslen; i++) {
collected += _executeUserOp(opIndex, ops[i], opInfos[opIndex]);
opIndex++;
}
}
emit SignatureAggregatorChanged(address(0));
_compensate(beneficiary, collected);
}
/**
* A memory copy of UserOp static fields only.
* Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster.
*/
struct MemoryUserOp {
address sender;
uint256 nonce;
uint256 verificationGasLimit;
uint256 callGasLimit;
uint256 paymasterVerificationGasLimit;
uint256 paymasterPostOpGasLimit;
uint256 preVerificationGas;
address paymaster;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
}
struct UserOpInfo {
MemoryUserOp mUserOp;
bytes32 userOpHash;
uint256 prefund;
uint256 contextOffset;
uint256 preOpGas;
}
/**
* Inner function to handle a UserOperation.
* Must be declared "external" to open a call context, but it can only be called by handleOps.
* @param callData - The callData to execute.
* @param opInfo - The UserOpInfo struct.
* @param context - The context bytes.
* @return actualGasCost - the actual cost in eth this UserOperation paid for gas
*/
function innerHandleOp(
bytes memory callData,
UserOpInfo memory opInfo,
bytes calldata context
) external returns (uint256 actualGasCost) {
uint256 preGas = gasleft();
require(msg.sender == address(this), "AA92 internal call only");
MemoryUserOp memory mUserOp = opInfo.mUserOp;
uint256 callGasLimit = mUserOp.callGasLimit;
unchecked {
// handleOps was called with gas limit too low. abort entire bundle.
if (
gasleft() * 63 / 64 <
callGasLimit +
mUserOp.paymasterPostOpGasLimit +
INNER_GAS_OVERHEAD
) {
assembly ("memory-safe") {
mstore(0, INNER_OUT_OF_GAS)
revert(0, 32)
}
}
}
IPaymaster.PostOpMode mode = IPaymaster.PostOpMode.opSucceeded;
if (callData.length > 0) {
bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit);
if (!success) {
bytes memory result = Exec.getReturnData(REVERT_REASON_MAX_LEN);
if (result.length > 0) {
emit UserOperationRevertReason(
opInfo.userOpHash,
mUserOp.sender,
mUserOp.nonce,
result
);
}
mode = IPaymaster.PostOpMode.opReverted;
}
}
unchecked {
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
return _postExecution(mode, opInfo, context, actualGas);
}
}
/// @inheritdoc IEntryPoint
function getUserOpHash(
PackedUserOperation calldata userOp
) public view returns (bytes32) {
return
keccak256(abi.encode(userOp.hash(), address(this), block.chainid));
}
/**
* Copy general fields from userOp into the memory opInfo structure.
* @param userOp - The user operation.
* @param mUserOp - The memory user operation.
*/
function _copyUserOpToMemory(
PackedUserOperation calldata userOp,
MemoryUserOp memory mUserOp
) internal pure {
mUserOp.sender = userOp.sender;
mUserOp.nonce = userOp.nonce;
(mUserOp.verificationGasLimit, mUserOp.callGasLimit) = UserOperationLib.unpackUints(userOp.accountGasLimits);
mUserOp.preVerificationGas = userOp.preVerificationGas;
(mUserOp.maxPriorityFeePerGas, mUserOp.maxFeePerGas) = UserOperationLib.unpackUints(userOp.gasFees);
bytes calldata paymasterAndData = userOp.paymasterAndData;
if (paymasterAndData.length > 0) {
require(
paymasterAndData.length >= UserOperationLib.PAYMASTER_DATA_OFFSET,
"AA93 invalid paymasterAndData"
);
(mUserOp.paymaster, mUserOp.paymasterVerificationGasLimit, mUserOp.paymasterPostOpGasLimit) = UserOperationLib.unpackPaymasterStaticFields(paymasterAndData);
} else {
mUserOp.paymaster = address(0);
mUserOp.paymasterVerificationGasLimit = 0;
mUserOp.paymasterPostOpGasLimit = 0;
}
}
/**
* Get the required prefunded gas fee amount for an operation.
* @param mUserOp - The user operation in memory.
*/
function _getRequiredPrefund(
MemoryUserOp memory mUserOp
) internal pure returns (uint256 requiredPrefund) {
unchecked {
uint256 requiredGas = mUserOp.verificationGasLimit +
mUserOp.callGasLimit +
mUserOp.paymasterVerificationGasLimit +
mUserOp.paymasterPostOpGasLimit +
mUserOp.preVerificationGas;
requiredPrefund = requiredGas * mUserOp.maxFeePerGas;
}
}
/**
* Create sender smart contract account if init code is provided.
* @param opIndex - The operation index.
* @param opInfo - The operation info.
* @param initCode - The init code for the smart contract account.
*/
function _createSenderIfNeeded(
uint256 opIndex,
UserOpInfo memory opInfo,
bytes calldata initCode
) internal {
if (initCode.length != 0) {
address sender = opInfo.mUserOp.sender;
if (sender.code.length != 0)
revert FailedOp(opIndex, "AA10 sender already constructed");
address sender1 = senderCreator().createSender{
gas: opInfo.mUserOp.verificationGasLimit
}(initCode);
if (sender1 == address(0))
revert FailedOp(opIndex, "AA13 initCode failed or OOG");
if (sender1 != sender)
revert FailedOp(opIndex, "AA14 initCode must return sender");
if (sender1.code.length == 0)
revert FailedOp(opIndex, "AA15 initCode must create sender");
address factory = address(bytes20(initCode[0:20]));
emit AccountDeployed(
opInfo.userOpHash,
sender,
factory,
opInfo.mUserOp.paymaster
);
}
}
/// @inheritdoc IEntryPoint
function getSenderAddress(bytes calldata initCode) public {
address sender = senderCreator().createSender(initCode);
revert SenderAddressResult(sender);
}
/**
* Call account.validateUserOp.
* Revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund.
* Decrement account's deposit if needed.
* @param opIndex - The operation index.
* @param op - The user operation.
* @param opInfo - The operation info.
* @param requiredPrefund - The required prefund amount.
*/
function _validateAccountPrepayment(
uint256 opIndex,
PackedUserOperation calldata op,
UserOpInfo memory opInfo,
uint256 requiredPrefund,
uint256 verificationGasLimit
)
internal
returns (
uint256 validationData
)
{
unchecked {
MemoryUserOp memory mUserOp = opInfo.mUserOp;
address sender = mUserOp.sender;
_createSenderIfNeeded(opIndex, opInfo, op.initCode);
address paymaster = mUserOp.paymaster;
uint256 missingAccountFunds = 0;
if (paymaster == address(0)) {
uint256 bal = balanceOf(sender);
missingAccountFunds = bal > requiredPrefund
? 0
: requiredPrefund - bal;
}
try
IAccount(sender).validateUserOp{
gas: verificationGasLimit
}(op, opInfo.userOpHash, missingAccountFunds)
returns (uint256 _validationData) {
validationData = _validationData;
} catch {
revert FailedOpWithRevert(opIndex, "AA23 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN));
}
if (paymaster == address(0)) {
DepositInfo storage senderInfo = deposits[sender];
uint256 deposit = senderInfo.deposit;
if (requiredPrefund > deposit) {
revert FailedOp(opIndex, "AA21 didn't pay prefund");
}
senderInfo.deposit = deposit - requiredPrefund;
}
}
}
/**
* In case the request has a paymaster:
* - Validate paymaster has enough deposit.
* - Call paymaster.validatePaymasterUserOp.
* - Revert with proper FailedOp in case paymaster reverts.
* - Decrement paymaster's deposit.
* @param opIndex - The operation index.
* @param op - The user operation.
* @param opInfo - The operation info.
* @param requiredPreFund - The required prefund amount.
*/
function _validatePaymasterPrepayment(
uint256 opIndex,
PackedUserOperation calldata op,
UserOpInfo memory opInfo,
uint256 requiredPreFund
) internal returns (bytes memory context, uint256 validationData) {
unchecked {
uint256 preGas = gasleft();
MemoryUserOp memory mUserOp = opInfo.mUserOp;
address paymaster = mUserOp.paymaster;
DepositInfo storage paymasterInfo = deposits[paymaster];
uint256 deposit = paymasterInfo.deposit;
if (deposit < requiredPreFund) {
revert FailedOp(opIndex, "AA31 paymaster deposit too low");
}
paymasterInfo.deposit = deposit - requiredPreFund;
uint256 pmVerificationGasLimit = mUserOp.paymasterVerificationGasLimit;
try
IPaymaster(paymaster).validatePaymasterUserOp{gas: pmVerificationGasLimit}(
op,
opInfo.userOpHash,
requiredPreFund
)
returns (bytes memory _context, uint256 _validationData) {
context = _context;
validationData = _validationData;
} catch {
revert FailedOpWithRevert(opIndex, "AA33 reverted", Exec.getReturnData(REVERT_REASON_MAX_LEN));
}
if (preGas - gasleft() > pmVerificationGasLimit) {
revert FailedOp(opIndex, "AA36 over paymasterVerificationGasLimit");
}
}
}
/**
* Revert if either account validationData or paymaster validationData is expired.
* @param opIndex - The operation index.
* @param validationData - The account validationData.
* @param paymasterValidationData - The paymaster validationData.
* @param expectedAggregator - The expected aggregator.
*/
function _validateAccountAndPaymasterValidationData(
uint256 opIndex,
uint256 validationData,
uint256 paymasterValidationData,
address expectedAggregator
) internal view {
(address aggregator, bool outOfTimeRange) = _getValidationData(
validationData
);
if (expectedAggregator != aggregator) {
revert FailedOp(opIndex, "AA24 signature error");
}
if (outOfTimeRange) {
revert FailedOp(opIndex, "AA22 expired or not due");
}
// pmAggregator is not a real signature aggregator: we don't have logic to handle it as address.
// Non-zero address means that the paymaster fails due to some signature check (which is ok only during estimation).
address pmAggregator;
(pmAggregator, outOfTimeRange) = _getValidationData(
paymasterValidationData
);
if (pmAggregator != address(0)) {
revert FailedOp(opIndex, "AA34 signature error");
}
if (outOfTimeRange) {
revert FailedOp(opIndex, "AA32 paymaster expired or not due");
}
}
/**
* Parse validationData into its components.
* @param validationData - The packed validation data (sigFailed, validAfter, validUntil).
* @return aggregator the aggregator of the validationData
* @return outOfTimeRange true if current time is outside the time range of this validationData.
*/
function _getValidationData(
uint256 validationData
) internal view returns (address aggregator, bool outOfTimeRange) {
if (validationData == 0) {
return (address(0), false);
}
ValidationData memory data = _parseValidationData(validationData);
// solhint-disable-next-line not-rely-on-time
outOfTimeRange = block.timestamp > data.validUntil || block.timestamp < data.validAfter;
aggregator = data.aggregator;
}
/**
* Validate account and paymaster (if defined) and
* also make sure total validation doesn't exceed verificationGasLimit.
* This method is called off-chain (simulateValidation()) and on-chain (from handleOps)
* @param opIndex - The index of this userOp into the "opInfos" array.
* @param userOp - The userOp to validate.
*/
function _validatePrepayment(
uint256 opIndex,
PackedUserOperation calldata userOp,
UserOpInfo memory outOpInfo
)
internal
returns (uint256 validationData, uint256 paymasterValidationData)
{
uint256 preGas = gasleft();
MemoryUserOp memory mUserOp = outOpInfo.mUserOp;
_copyUserOpToMemory(userOp, mUserOp);
outOpInfo.userOpHash = getUserOpHash(userOp);
// Validate all numeric values in userOp are well below 128 bit, so they can safely be added
// and multiplied without causing overflow.
uint256 verificationGasLimit = mUserOp.verificationGasLimit;
uint256 maxGasValues = mUserOp.preVerificationGas |
verificationGasLimit |
mUserOp.callGasLimit |
mUserOp.paymasterVerificationGasLimit |
mUserOp.paymasterPostOpGasLimit |
mUserOp.maxFeePerGas |
mUserOp.maxPriorityFeePerGas;
require(maxGasValues <= type(uint120).max, "AA94 gas values overflow");
uint256 requiredPreFund = _getRequiredPrefund(mUserOp);
validationData = _validateAccountPrepayment(
opIndex,
userOp,
outOpInfo,
requiredPreFund,
verificationGasLimit
);
if (!_validateAndUpdateNonce(mUserOp.sender, mUserOp.nonce)) {
revert FailedOp(opIndex, "AA25 invalid account nonce");
}
unchecked {
if (preGas - gasleft() > verificationGasLimit) {
revert FailedOp(opIndex, "AA26 over verificationGasLimit");
}
}
bytes memory context;
if (mUserOp.paymaster != address(0)) {
(context, paymasterValidationData) = _validatePaymasterPrepayment(
opIndex,
userOp,
outOpInfo,
requiredPreFund
);
}
unchecked {
outOpInfo.prefund = requiredPreFund;
outOpInfo.contextOffset = getOffsetOfMemoryBytes(context);
outOpInfo.preOpGas = preGas - gasleft() + userOp.preVerificationGas;
}
}
/**
* Process post-operation, called just after the callData is executed.
* If a paymaster is defined and its validation returned a non-empty context, its postOp is called.
* The excess amount is refunded to the account (or paymaster - if it was used in the request).
* @param mode - Whether is called from innerHandleOp, or outside (postOpReverted).
* @param opInfo - UserOp fields and info collected during validation.
* @param context - The context returned in validatePaymasterUserOp.
* @param actualGas - The gas used so far by this user operation.
*/
function _postExecution(
IPaymaster.PostOpMode mode,
UserOpInfo memory opInfo,
bytes memory context,
uint256 actualGas
) private returns (uint256 actualGasCost) {
uint256 preGas = gasleft();
unchecked {
address refundAddress;
MemoryUserOp memory mUserOp = opInfo.mUserOp;
uint256 gasPrice = getUserOpGasPrice(mUserOp);
address paymaster = mUserOp.paymaster;
if (paymaster == address(0)) {
refundAddress = mUserOp.sender;
} else {
refundAddress = paymaster;
if (context.length > 0) {
actualGasCost = actualGas * gasPrice;
if (mode != IPaymaster.PostOpMode.postOpReverted) {
try IPaymaster(paymaster).postOp{
gas: mUserOp.paymasterPostOpGasLimit
}(mode, context, actualGasCost, gasPrice)
// solhint-disable-next-line no-empty-blocks
{} catch {
bytes memory reason = Exec.getReturnData(REVERT_REASON_MAX_LEN);
revert PostOpReverted(reason);
}
}
}
}
actualGas += preGas - gasleft();
// Calculating a penalty for unused execution gas
{
uint256 executionGasLimit = mUserOp.callGasLimit + mUserOp.paymasterPostOpGasLimit;
uint256 executionGasUsed = actualGas - opInfo.preOpGas;
// this check is required for the gas used within EntryPoint and not covered by explicit gas limits
if (executionGasLimit > executionGasUsed) {
uint256 unusedGas = executionGasLimit - executionGasUsed;
uint256 unusedGasPenalty = (unusedGas * PENALTY_PERCENT) / 100;
actualGas += unusedGasPenalty;
}
}
actualGasCost = actualGas * gasPrice;
uint256 prefund = opInfo.prefund;
if (prefund < actualGasCost) {
if (mode == IPaymaster.PostOpMode.postOpReverted) {
actualGasCost = prefund;
emitPrefundTooLow(opInfo);
emitUserOperationEvent(opInfo, false, actualGasCost, actualGas);
} else {
assembly ("memory-safe") {
mstore(0, INNER_REVERT_LOW_PREFUND)
revert(0, 32)
}
}
} else {
uint256 refund = prefund - actualGasCost;
_incrementDeposit(refundAddress, refund);
bool success = mode == IPaymaster.PostOpMode.opSucceeded;
emitUserOperationEvent(opInfo, success, actualGasCost, actualGas);
}
} // unchecked
}
/**
* The gas price this UserOp agrees to pay.
* Relayer/block builder might submit the TX with higher priorityFee, but the user should not.
* @param mUserOp - The userOp to get the gas price from.
*/
function getUserOpGasPrice(
MemoryUserOp memory mUserOp
) internal view returns (uint256) {
unchecked {
uint256 maxFeePerGas = mUserOp.maxFeePerGas;
uint256 maxPriorityFeePerGas = mUserOp.maxPriorityFeePerGas;
if (maxFeePerGas == maxPriorityFeePerGas) {
//legacy mode (for networks that don't support basefee opcode)
return maxFeePerGas;
}
return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
}
}
/**
* The offset of the given bytes in memory.
* @param data - The bytes to get the offset of.
*/
function getOffsetOfMemoryBytes(
bytes memory data
) internal pure returns (uint256 offset) {
assembly {
offset := data
}
}
/**
* The bytes in memory at the given offset.
* @param offset - The offset to get the bytes from.
*/
function getMemoryBytesFromOffset(
uint256 offset
) internal pure returns (bytes memory data) {
assembly ("memory-safe") {
data := offset
}
}
/// @inheritdoc IEntryPoint
function delegateAndRevert(address target, bytes calldata data) external {
(bool success, bytes memory ret) = target.delegatecall(data);
revert DelegateAndRevert(success, ret);
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/* solhint-disable no-inline-assembly */
/*
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
* must return this value in case of signature failure, instead of revert.
*/
uint256 constant SIG_VALIDATION_FAILED = 1;
/*
* For simulation purposes, validateUserOp (and validatePaymasterUserOp)
* return this value on success.
*/
uint256 constant SIG_VALIDATION_SUCCESS = 0;
/**
* Returned data from validateUserOp.
* validateUserOp returns a uint256, which is created by `_packedValidationData` and
* parsed by `_parseValidationData`.
* @param aggregator - address(0) - The account validated the signature by itself.
* address(1) - The account failed to validate the signature.
* otherwise - This is an address of a signature aggregator that must
* be used to validate the signature.
* @param validAfter - This UserOp is valid only after this timestamp.
* @param validaUntil - This UserOp is valid only up to this timestamp.
*/
struct ValidationData {
address aggregator;
uint48 validAfter;
uint48 validUntil;
}
/**
* Extract sigFailed, validAfter, validUntil.
* Also convert zero validUntil to type(uint48).max.
* @param validationData - The packed validation data.
*/
function _parseValidationData(
uint256 validationData
) pure returns (ValidationData memory data) {
address aggregator = address(uint160(validationData));
uint48 validUntil = uint48(validationData >> 160);
if (validUntil == 0) {
validUntil = type(uint48).max;
}
uint48 validAfter = uint48(validationData >> (48 + 160));
return ValidationData(aggregator, validAfter, validUntil);
}
/**
* Helper to pack the return value for validateUserOp.
* @param data - The ValidationData to pack.
*/
function _packValidationData(
ValidationData memory data
) pure returns (uint256) {
return
uint160(data.aggregator) |
(uint256(data.validUntil) << 160) |
(uint256(data.validAfter) << (160 + 48));
}
/**
* Helper to pack the return value for validateUserOp, when not using an aggregator.
* @param sigFailed - True for signature failure, false for success.
* @param validUntil - Last timestamp this UserOperation is valid (or zero for infinite).
* @param validAfter - First timestamp this UserOperation is valid.
*/
function _packValidationData(
bool sigFailed,
uint48 validUntil,
uint48 validAfter
) pure returns (uint256) {
return
(sigFailed ? 1 : 0) |
(uint256(validUntil) << 160) |
(uint256(validAfter) << (160 + 48));
}
/**
* keccak function over calldata.
* @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting solidity do it.
*/
function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) {
assembly ("memory-safe") {
let mem := mload(0x40)
let len := data.length
calldatacopy(mem, data.offset, len)
ret := keccak256(mem, len)
}
}
/**
* The minimum of two numbers.
* @param a - First number.
* @param b - Second number.
*/
function min(uint256 a, uint256 b) pure returns (uint256) {
return a < b ? a : b;
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
import "../interfaces/INonceManager.sol";
/**
* nonce management functionality
*/
abstract contract NonceManager is INonceManager {
/**
* The next valid sequence number for a given nonce key.
*/
mapping(address => mapping(uint192 => uint256)) public nonceSequenceNumber;
/// @inheritdoc INonceManager
function getNonce(address sender, uint192 key)
public view override returns (uint256 nonce) {
return nonceSequenceNumber[sender][key] | (uint256(key) << 64);
}
// allow an account to manually increment its own nonce.
// (mainly so that during construction nonce can be made non-zero,
// to "absorb" the gas cost of first nonce increment to 1st transaction (construction),
// not to 2nd transaction)
function incrementNonce(uint192 key) public override {
nonceSequenceNumber[msg.sender][key]++;
}
/**
* validate nonce uniqueness for this account.
* called just after validateUserOp()
* @return true if the nonce was incremented successfully.
* false if the current nonce doesn't match the given one.
*/
function _validateAndUpdateNonce(address sender, uint256 nonce) internal returns (bool) {
uint192 key = uint192(nonce >> 64);
uint64 seq = uint64(nonce);
return nonceSequenceNumber[sender][key]++ == seq;
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/**
* Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address,
* which is explicitly not the entryPoint itself.
*/
contract SenderCreator {
/**
* Call the "initCode" factory to create and return the sender account address.
* @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address,
* followed by calldata.
* @return sender - The returned address of the created account, or zero address on failure.
*/
function createSender(
bytes calldata initCode
) external returns (address sender) {
address factory = address(bytes20(initCode[0:20]));
bytes memory initCallData = initCode[20:];
bool success;
/* solhint-disable no-inline-assembly */
assembly ("memory-safe") {
success := call(
gas(),
factory,
0,
add(initCallData, 0x20),
mload(initCallData),
0,
32
)
sender := mload(0)
}
if (!success) {
sender = address(0);
}
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.23;
import "../interfaces/IStakeManager.sol";
/* solhint-disable avoid-low-level-calls */
/* solhint-disable not-rely-on-time */
/**
* Manage deposits and stakes.
* Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account).
* Stake is value locked for at least "unstakeDelay" by a paymaster.
*/
abstract contract StakeManager is IStakeManager {
/// maps paymaster to their deposits and stakes
mapping(address => DepositInfo) public deposits;
/// @inheritdoc IStakeManager
function getDepositInfo(
address account
) public view returns (DepositInfo memory info) {
return deposits[account];
}
/**
* Internal method to return just the stake info.
* @param addr - The account to query.
*/
function _getStakeInfo(
address addr
) internal view returns (StakeInfo memory info) {
DepositInfo storage depositInfo = deposits[addr];
info.stake = depositInfo.stake;
info.unstakeDelaySec = depositInfo.unstakeDelaySec;
}
/// @inheritdoc IStakeManager
function balanceOf(address account) public view returns (uint256) {
return deposits[account].deposit;
}
receive() external payable {
depositTo(msg.sender);
}
/**
* Increments an account's deposit.
* @param account - The account to increment.
* @param amount - The amount to increment by.
* @return the updated deposit of this account
*/
function _incrementDeposit(address account, uint256 amount) internal returns (uint256) {
DepositInfo storage info = deposits[account];
uint256 newAmount = info.deposit + amount;
info.deposit = newAmount;
return newAmount;
}
/**
* Add to the deposit of the given account.
* @param account - The account to add to.
*/
function depositTo(address account) public virtual payable {
uint256 newDeposit = _incrementDeposit(account, msg.value);
emit Deposited(account, newDeposit);
}
/**
* Add to the account's stake - amount and delay
* any pending unstake is first cancelled.
* @param unstakeDelaySec The new lock duration before the deposit can be withdrawn.
*/
function addStake(uint32 unstakeDelaySec) public payable {
DepositInfo storage info = deposits[msg.sender];
require(unstakeDelaySec > 0, "must specify unstake delay");
require(
unstakeDelaySec >= info.unstakeDelaySec,
"cannot decrease unstake time"
);
uint256 stake = info.stake + msg.value;
require(stake > 0, "no stake specified");
require(stake <= type(uint112).max, "stake overflow");
deposits[msg.sender] = DepositInfo(
info.deposit,
true,
uint112(stake),
unstakeDelaySec,
0
);
emit StakeLocked(msg.sender, stake, unstakeDelaySec);
}
/**
* Attempt to unlock the stake.
* The value can be withdrawn (using withdrawStake) after the unstake delay.
*/
function unlockStake() external {
DepositInfo storage info = deposits[msg.sender];
require(info.unstakeDelaySec != 0, "not staked");
require(info.staked, "already unstaking");
uint48 withdrawTime = uint48(block.timestamp) + info.unstakeDelaySec;
info.withdrawTime = withdrawTime;
info.staked = false;
emit StakeUnlocked(msg.sender, withdrawTime);
}
/**
* Withdraw from the (unlocked) stake.
* Must first call unlockStake and wait for the unstakeDelay to pass.
* @param withdrawAddress - The address to send withdrawn value.
*/
function withdrawStake(address payable withdrawAddress) external {
DepositInfo storage info = deposits[msg.sender];
uint256 stake = info.stake;
require(stake > 0, "No stake to withdraw");
require(info.withdrawTime > 0, "must call unlockStake() first");
require(
info.withdrawTime <= block.timestamp,
"Stake withdrawal is not due"
);
info.unstakeDelaySec = 0;
info.withdrawTime = 0;
info.stake = 0;
emit StakeWithdrawn(msg.sender, withdrawAddress, stake);
(bool success,) = withdrawAddress.call{value: stake}("");
require(success, "failed to withdraw stake");
}
/**
* Withdraw from the deposit.
* @param withdrawAddress - The address to send withdrawn value.
* @param withdrawAmount - The amount to withdraw.
*/
function withdrawTo(
address payable withdrawAddress,
uint256 withdrawAmount
) external {
DepositInfo storage info = deposits[msg.sender];
require(withdrawAmount <= info.deposit, "Withdraw amount too large");
info.deposit = info.deposit - withdrawAmount;
emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount);
(bool success,) = withdrawAddress.call{value: withdrawAmount}("");
require(success, "failed to withdraw");
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
/* solhint-disable no-inline-assembly */
import "../interfaces/PackedUserOperation.sol";
import {calldataKeccak, min} from "./Helpers.sol";
/**
* Utility functions helpful when working with UserOperation structs.
*/
library UserOperationLib {
uint256 public constant PAYMASTER_VALIDATION_GAS_OFFSET = 20;
uint256 public constant PAYMASTER_POSTOP_GAS_OFFSET = 36;
uint256 public constant PAYMASTER_DATA_OFFSET = 52;
/**
* Get sender from user operation data.
* @param userOp - The user operation data.
*/
function getSender(
PackedUserOperation calldata userOp
) internal pure returns (address) {
address data;
//read sender from userOp, which is first userOp member (saves 800 gas...)
assembly {
data := calldataload(userOp)
}
return address(uint160(data));
}
/**
* Relayer/block builder might submit the TX with higher priorityFee,
* but the user should not pay above what he signed for.
* @param userOp - The user operation data.
*/
function gasPrice(
PackedUserOperation calldata userOp
) internal view returns (uint256) {
unchecked {
(uint256 maxPriorityFeePerGas, uint256 maxFeePerGas) = unpackUints(userOp.gasFees);
if (maxFeePerGas == maxPriorityFeePerGas) {
//legacy mode (for networks that don't support basefee opcode)
return maxFeePerGas;
}
return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
}
}
/**
* Pack the user operation data into bytes for hashing.
* @param userOp - The user operation data.
*/
function encode(
PackedUserOperation calldata userOp
) internal pure returns (bytes memory ret) {
address sender = getSender(userOp);
uint256 nonce = userOp.nonce;
bytes32 hashInitCode = calldataKeccak(userOp.initCode);
bytes32 hashCallData = calldataKeccak(userOp.callData);
bytes32 accountGasLimits = userOp.accountGasLimits;
uint256 preVerificationGas = userOp.preVerificationGas;
bytes32 gasFees = userOp.gasFees;
bytes32 hashPaymasterAndData = calldataKeccak(userOp.paymasterAndData);
return abi.encode(
sender, nonce,
hashInitCode, hashCallData,
accountGasLimits, preVerificationGas, gasFees,
hashPaymasterAndData
);
}
function unpackUints(
bytes32 packed
) internal pure returns (uint256 high128, uint256 low128) {
return (uint128(bytes16(packed)), uint128(uint256(packed)));
}
//unpack just the high 128-bits from a packed value
function unpackHigh128(bytes32 packed) internal pure returns (uint256) {
return uint256(packed) >> 128;
}
// unpack just the low 128-bits from a packed value
function unpackLow128(bytes32 packed) internal pure returns (uint256) {
return uint128(uint256(packed));
}
function unpackMaxPriorityFeePerGas(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackHigh128(userOp.gasFees);
}
function unpackMaxFeePerGas(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackLow128(userOp.gasFees);
}
function unpackVerificationGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackHigh128(userOp.accountGasLimits);
}
function unpackCallGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return unpackLow128(userOp.accountGasLimits);
}
function unpackPaymasterVerificationGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_POSTOP_GAS_OFFSET]));
}
function unpackPostOpGasLimit(PackedUserOperation calldata userOp)
internal pure returns (uint256) {
return uint128(bytes16(userOp.paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET : PAYMASTER_DATA_OFFSET]));
}
function unpackPaymasterStaticFields(
bytes calldata paymasterAndData
) internal pure returns (address paymaster, uint256 validationGasLimit, uint256 postOpGasLimit) {
return (
address(bytes20(paymasterAndData[: PAYMASTER_VALIDATION_GAS_OFFSET])),
uint128(bytes16(paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_POSTOP_GAS_OFFSET])),
uint128(bytes16(paymasterAndData[PAYMASTER_POSTOP_GAS_OFFSET : PAYMASTER_DATA_OFFSET]))
);
}
/**
* Hash the user operation data.
* @param userOp - The user operation data.
*/
function hash(
PackedUserOperation calldata userOp
) internal pure returns (bytes32) {
return keccak256(encode(userOp));
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
interface IAccount {
/**
* Validate user's signature and nonce
* the entryPoint will make the call to the recipient only if this validation call returns successfully.
* signature failure should be reported by returning SIG_VALIDATION_FAILED (1).
* This allows making a "simulation call" without a valid signature
* Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure.
*
* @dev Must validate caller is the entryPoint.
* Must validate the signature and nonce
* @param userOp - The operation that is about to be executed.
* @param userOpHash - Hash of the user's request data. can be used as the basis for signature.
* @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint.
* This is the minimum amount to transfer to the sender(entryPoint) to be
* able to make the call. The excess is left as a deposit in the entrypoint
* for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()".
* In case there is a paymaster in the request (or the current deposit is high
* enough), this value will be zero.
* @return validationData - Packaged ValidationData structure. use `_packValidationData` and
* `_unpackValidationData` to encode and decode.
* <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
* If an account doesn't use time-range, it is enough to
* return SIG_VALIDATION_FAILED value (1) for signature failure.
* Note that the validation code cannot use block.timestamp (or block.number) directly.
*/
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData);
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
interface IAccountExecute {
/**
* Account may implement this execute method.
* passing this methodSig at the beginning of callData will cause the entryPoint to pass the full UserOp (and hash)
* to the account.
* The account should skip the methodSig, and use the callData (and optionally, other UserOp fields)
*
* @param userOp - The operation that was just validated.
* @param userOpHash - Hash of the user's request data.
*/
function executeUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external;
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
import "./PackedUserOperation.sol";
/**
* Aggregated Signatures validator.
*/
interface IAggregator {
/**
* Validate aggregated signature.
* Revert if the aggregated signature does not match the given list of operations.
* @param userOps - Array of UserOperations to validate the signature for.
* @param signature - The aggregated signature.
*/
function validateSignatures(
PackedUserOperation[] calldata userOps,
bytes calldata signature
) external view;
/**
* Validate signature of a single userOp.
* This method should be called by bundler after EntryPointSimulation.simulateValidation() returns
* the aggregator this account uses.
* First it validates the signature over the userOp. Then it returns data to be used when creating the handleOps.
* @param userOp - The userOperation received from the user.
* @return sigForUserOp - The value to put into the signature field of the userOp when calling handleOps.
* (usually empty, unless account and aggregator support some kind of "multisig".
*/
function validateUserOpSignature(
PackedUserOperation calldata userOp
) external view returns (bytes memory sigForUserOp);
/**
* Aggregate multiple signatures into a single value.
* This method is called off-chain to calculate the signature to pass with handleOps()
* bundler MAY use optimized custom code perform this aggregation.
* @param userOps - Array of UserOperations to collect the signatures from.
* @return aggregatedSignature - The aggregated signature.
*/
function aggregateSignatures(
PackedUserOperation[] calldata userOps
) external view returns (bytes memory aggregatedSignature);
}
/**
** Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
** Only one instance required on each chain.
**/
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */
import "./PackedUserOperation.sol";
import "./IStakeManager.sol";
import "./IAggregator.sol";
import "./INonceManager.sol";
interface IEntryPoint is IStakeManager, INonceManager {
/***
* An event emitted after each successful request.
* @param userOpHash - Unique identifier for the request (hash its entire content, except signature).
* @param sender - The account that generates this request.
* @param paymaster - If non-null, the paymaster that pays for this request.
* @param nonce - The nonce value from the request.
* @param success - True if the sender transaction succeeded, false if reverted.
* @param actualGasCost - Actual amount paid (by account or paymaster) for this UserOperation.
* @param actualGasUsed - Total gas used by this UserOperation (including preVerification, creation,
* validation and execution).
*/
event UserOperationEvent(
bytes32 indexed userOpHash,
address indexed sender,
address indexed paymaster,
uint256 nonce,
bool success,
uint256 actualGasCost,
uint256 actualGasUsed
);
/**
* Account "sender" was deployed.
* @param userOpHash - The userOp that deployed this account. UserOperationEvent will follow.
* @param sender - The account that is deployed
* @param factory - The factory used to deploy this account (in the initCode)
* @param paymaster - The paymaster used by this UserOp
*/
event AccountDeployed(
bytes32 indexed userOpHash,
address indexed sender,
address factory,
address paymaster
);
/**
* An event emitted if the UserOperation "callData" reverted with non-zero length.
* @param userOpHash - The request unique identifier.
* @param sender - The sender of this request.
* @param nonce - The nonce used in the request.
* @param revertReason - The return bytes from the (reverted) call to "callData".
*/
event UserOperationRevertReason(
bytes32 indexed userOpHash,
address indexed sender,
uint256 nonce,
bytes revertReason
);
/**
* An event emitted if the UserOperation Paymaster's "postOp" call reverted with non-zero length.
* @param userOpHash - The request unique identifier.
* @param sender - The sender of this request.
* @param nonce - The nonce used in the request.
* @param revertReason - The return bytes from the (reverted) call to "callData".
*/
event PostOpRevertReason(
bytes32 indexed userOpHash,
address indexed sender,
uint256 nonce,
bytes revertReason
);
/**
* UserOp consumed more than prefund. The UserOperation is reverted, and no refund is made.
* @param userOpHash - The request unique identifier.
* @param sender - The sender of this request.
* @param nonce - The nonce used in the request.
*/
event UserOperationPrefundTooLow(
bytes32 indexed userOpHash,
address indexed sender,
uint256 nonce
);
/**
* An event emitted by handleOps(), before starting the execution loop.
* Any event emitted before this event, is part of the validation.
*/
event BeforeExecution();
/**
* Signature aggregator used by the following UserOperationEvents within this bundle.
* @param aggregator - The aggregator used for the following UserOperationEvents.
*/
event SignatureAggregatorChanged(address indexed aggregator);
/**
* A custom revert error of handleOps, to identify the offending op.
* Should be caught in off-chain handleOps simulation and not happen on-chain.
* Useful for mitigating DoS attempts against batchers or for troubleshooting of factory/account/paymaster reverts.
* NOTE: If simulateValidation passes successfully, there should be no reason for handleOps to fail on it.
* @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero).
* @param reason - Revert reason. The string starts with a unique code "AAmn",
* where "m" is "1" for factory, "2" for account and "3" for paymaster issues,
* so a failure can be attributed to the correct entity.
*/
error FailedOp(uint256 opIndex, string reason);
/**
* A custom revert error of handleOps, to report a revert by account or paymaster.
* @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero).
* @param reason - Revert reason. see FailedOp(uint256,string), above
* @param inner - data from inner cought revert reason
* @dev note that inner is truncated to 2048 bytes
*/
error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner);
error PostOpReverted(bytes returnData);
/**
* Error case when a signature aggregator fails to verify the aggregated signature it had created.
* @param aggregator The aggregator that failed to verify the signature
*/
error SignatureValidationFailed(address aggregator);
// Return value of getSenderAddress.
error SenderAddressResult(address sender);
// UserOps handled, per aggregator.
struct UserOpsPerAggregator {
PackedUserOperation[] userOps;
// Aggregator address
IAggregator aggregator;
// Aggregated signature
bytes signature;
}
/**
* Execute a batch of UserOperations.
* No signature aggregator is used.
* If any account requires an aggregator (that is, it returned an aggregator when
* performing simulateValidation), then handleAggregatedOps() must be used instead.
* @param ops - The operations to execute.
* @param beneficiary - The address to receive the fees.
*/
function handleOps(
PackedUserOperation[] calldata ops,
address payable beneficiary
) external;
/**
* Execute a batch of UserOperation with Aggregators
* @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts).
* @param beneficiary - The address to receive the fees.
*/
function handleAggregatedOps(
UserOpsPerAggregator[] calldata opsPerAggregator,
address payable beneficiary
) external;
/**
* Generate a request Id - unique identifier for this request.
* The request ID is a hash over the content of the userOp (except the signature), the entrypoint and the chainid.
* @param userOp - The user operation to generate the request ID for.
* @return hash the hash of this UserOperation
*/
function getUserOpHash(
PackedUserOperation calldata userOp
) external view returns (bytes32);
/**
* Gas and return values during simulation.
* @param preOpGas - The gas used for validation (including preValidationGas)
* @param prefund - The required prefund for this operation
* @param accountValidationData - returned validationData from account.
* @param paymasterValidationData - return validationData from paymaster.
* @param paymasterContext - Returned by validatePaymasterUserOp (to be passed into postOp)
*/
struct ReturnInfo {
uint256 preOpGas;
uint256 prefund;
uint256 accountValidationData;
uint256 paymasterValidationData;
bytes paymasterContext;
}
/**
* Returned aggregated signature info:
* The aggregator returned by the account, and its current stake.
*/
struct AggregatorStakeInfo {
address aggregator;
StakeInfo stakeInfo;
}
/**
* Get counterfactual sender address.
* Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation.
* This method always revert, and returns the address in SenderAddressResult error
* @param initCode - The constructor code to be passed into the UserOperation.
*/
function getSenderAddress(bytes memory initCode) external;
error DelegateAndRevert(bool success, bytes ret);
/**
* Helper method for dry-run testing.
* @dev calling this method, the EntryPoint will make a delegatecall to the given data, and report (via revert) the result.
* The method always revert, so is only useful off-chain for dry run calls, in cases where state-override to replace
* actual EntryPoint code is less convenient.
* @param target a target contract to make a delegatecall from entrypoint
* @param data data to pass to target in a delegatecall
*/
function delegateAndRevert(address target, bytes calldata data) external;
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
interface INonceManager {
/**
* Return the next nonce for this sender.
* Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop)
* But UserOp with different keys can come with arbitrary order.
*
* @param sender the account address
* @param key the high 192 bit of the nonce
* @return nonce a full nonce to pass for next UserOp with this sender.
*/
function getNonce(address sender, uint192 key)
external view returns (uint256 nonce);
/**
* Manually increment the nonce of the sender.
* This method is exposed just for completeness..
* Account does NOT need to call it, neither during validation, nor elsewhere,
* as the EntryPoint will update the nonce regardless.
* Possible use-case is call it with various keys to "initialize" their nonces to one, so that future
* UserOperations will not pay extra for the first transaction with a given key.
*/
function incrementNonce(uint192 key) external;
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
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 {
// User op succeeded.
opSucceeded,
// User op reverted. Still has to pay for gas.
opReverted,
// Only used internally in the EntryPoint (cleanup after postOp reverts). Never calling paymaster with this value
postOpReverted
}
/**
* 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,
* other values are invalid for paymaster.
* <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. The paymaster still has to pay for gas.
* postOpReverted - never passed in a call to postOp().
* @param context - The context value returned by validatePaymasterUserOp
* @param actualGasCost - Actual gas used so far (without this postOp call).
* @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas
* and maxPriorityFee (and basefee)
* It is not the same as tx.gasprice, which is what the bundler pays.
*/
function postOp(
PostOpMode mode,
bytes calldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.7.5;
/**
* Manage deposits and stakes.
* Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account).
* Stake is value locked for at least "unstakeDelay" by the staked entity.
*/
interface IStakeManager {
event Deposited(address indexed account, uint256 totalDeposit);
event Withdrawn(
address indexed account,
address withdrawAddress,
uint256 amount
);
// Emitted when stake or unstake delay are modified.
event StakeLocked(
address indexed account,
uint256 totalStaked,
uint256 unstakeDelaySec
);
// Emitted once a stake is scheduled for withdrawal.
event StakeUnlocked(address indexed account, uint256 withdrawTime);
event StakeWithdrawn(
address indexed account,
address withdrawAddress,
uint256 amount
);
/**
* @param deposit - The entity's deposit.
* @param staked - True if this entity is staked.
* @param stake - Actual amount of ether staked for this entity.
* @param unstakeDelaySec - Minimum delay to withdraw the stake.
* @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked.
* @dev Sizes were chosen so that deposit fits into one cell (used during handleOp)
* and the rest fit into a 2nd cell (used during stake/unstake)
* - 112 bit allows for 10^15 eth
* - 48 bit for full timestamp
* - 32 bit allows 150 years for unstake delay
*/
struct DepositInfo {
uint256 deposit;
bool staked;
uint112 stake;
uint32 unstakeDelaySec;
uint48 withdrawTime;
}
// API struct used by getStakeInfo and simulateValidation.
struct StakeInfo {
uint256 stake;
uint256 unstakeDelaySec;
}
/**
* Get deposit info.
* @param account - The account to query.
* @return info - Full deposit information of given account.
*/
function getDepositInfo(
address account
) external view returns (DepositInfo memory info);
/**
* Get account balance.
* @param account - The account to query.
* @return - The deposit (for gas payment) of the account.
*/
function balanceOf(address account) external view returns (uint256);
/**
* Add to the deposit of the given account.
* @param account - The account to add to.
*/
function depositTo(address account) external payable;
/**
* Add to the account's stake - amount and delay
* any pending unstake is first cancelled.
* @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn.
*/
function addStake(uint32 _unstakeDelaySec) external payable;
/**
* Attempt to unlock the stake.
* The value can be withdrawn (using withdrawStake) after the unstake delay.
*/
function unlockStake() external;
/**
* Withdraw from the (unlocked) stake.
* Must first call unlockStake and wait for the unstakeDelay to pass.
* @param withdrawAddress - The address to send withdrawn value.
*/
function withdrawStake(address payable withdrawAddress) external;
/**
* Withdraw from the deposit.
* @param withdrawAddress - The address to send withdrawn value.
* @param withdrawAmount - The amount to withdraw.
*/
function withdrawTo(
address payable withdrawAddress,
uint256 withdrawAmount
) external;
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5;
/**
* 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;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.23;
// solhint-disable no-inline-assembly
/**
* Utility functions helpful when making different kinds of contract calls in Solidity.
*/
library Exec {
function call(
address to,
uint256 value,
bytes memory data,
uint256 txGas
) internal returns (bool success) {
assembly ("memory-safe") {
success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
}
}
function staticcall(
address to,
bytes memory data,
uint256 txGas
) internal view returns (bool success) {
assembly ("memory-safe") {
success := staticcall(txGas, to, add(data, 0x20), mload(data), 0, 0)
}
}
function delegateCall(
address to,
bytes memory data,
uint256 txGas
) internal returns (bool success) {
assembly ("memory-safe") {
success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
}
}
// get returned data from last call or calldelegate
function getReturnData(uint256 maxLen) internal pure returns (bytes memory returnData) {
assembly ("memory-safe") {
let len := returndatasize()
if gt(len, maxLen) {
len := maxLen
}
let ptr := mload(0x40)
mstore(0x40, add(ptr, add(len, 0x20)))
mstore(ptr, len)
returndatacopy(add(ptr, 0x20), 0, len)
returnData := ptr
}
}
// revert with explicit byte array (probably reverted info from call)
function revertWithData(bytes memory returnData) internal pure {
assembly ("memory-safe") {
revert(add(returnData, 32), mload(returnData))
}
}
function callAndRevert(address to, bytes memory data, uint256 maxLen) internal {
bool success = call(to,0,data,gasleft());
if (!success) {
revertWithData(getReturnData(maxLen));
}
}
}
File 2 of 3: TetherToken
pragma solidity ^0.4.17;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
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 c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 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;
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() 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 {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20Basic {
uint public _totalSupply;
function totalSupply() public constant returns (uint);
function balanceOf(address who) public constant returns (uint);
function transfer(address to, uint value) public;
event Transfer(address indexed from, address indexed to, uint value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) public constant returns (uint);
function transferFrom(address from, address to, uint value) public;
function approve(address spender, uint value) public;
event Approval(address indexed owner, address indexed spender, uint value);
}
/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
contract BasicToken is Ownable, ERC20Basic {
using SafeMath for uint;
mapping(address => uint) public balances;
// additional variables for use if transaction fees ever became necessary
uint public basisPointsRate = 0;
uint public maximumFee = 0;
/**
* @dev Fix for the ERC20 short address attack.
*/
modifier onlyPayloadSize(uint size) {
require(!(msg.data.length < size + 4));
_;
}
/**
* @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, uint _value) public onlyPayloadSize(2 * 32) {
uint fee = (_value.mul(basisPointsRate)).div(10000);
if (fee > maximumFee) {
fee = maximumFee;
}
uint sendAmount = _value.sub(fee);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(sendAmount);
if (fee > 0) {
balances[owner] = balances[owner].add(fee);
Transfer(msg.sender, owner, fee);
}
Transfer(msg.sender, _to, sendAmount);
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public constant returns (uint balance) {
return balances[_owner];
}
}
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* @dev https://github.com/ethereum/EIPs/issues/20
* @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract StandardToken is BasicToken, ERC20 {
mapping (address => mapping (address => uint)) public allowed;
uint public constant MAX_UINT = 2**256 - 1;
/**
* @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 uint the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
var _allowance = allowed[_from][msg.sender];
// Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
// if (_value > _allowance) throw;
uint fee = (_value.mul(basisPointsRate)).div(10000);
if (fee > maximumFee) {
fee = maximumFee;
}
if (_allowance < MAX_UINT) {
allowed[_from][msg.sender] = _allowance.sub(_value);
}
uint sendAmount = _value.sub(fee);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(sendAmount);
if (fee > 0) {
balances[owner] = balances[owner].add(fee);
Transfer(_from, owner, fee);
}
Transfer(_from, _to, sendAmount);
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}
/**
* @dev Function to check the amount of tokens than 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 uint specifying the amount of tokens still available for the spender.
*/
function allowance(address _owner, address _spender) public constant returns (uint remaining) {
return allowed[_owner][_spender];
}
}
/**
* @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;
Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}
contract BlackList is Ownable, BasicToken {
/////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
function getBlackListStatus(address _maker) external constant returns (bool) {
return isBlackListed[_maker];
}
function getOwner() external constant returns (address) {
return owner;
}
mapping (address => bool) public isBlackListed;
function addBlackList (address _evilUser) public onlyOwner {
isBlackListed[_evilUser] = true;
AddedBlackList(_evilUser);
}
function removeBlackList (address _clearedUser) public onlyOwner {
isBlackListed[_clearedUser] = false;
RemovedBlackList(_clearedUser);
}
function destroyBlackFunds (address _blackListedUser) public onlyOwner {
require(isBlackListed[_blackListedUser]);
uint dirtyFunds = balanceOf(_blackListedUser);
balances[_blackListedUser] = 0;
_totalSupply -= dirtyFunds;
DestroyedBlackFunds(_blackListedUser, dirtyFunds);
}
event DestroyedBlackFunds(address _blackListedUser, uint _balance);
event AddedBlackList(address _user);
event RemovedBlackList(address _user);
}
contract UpgradedStandardToken is StandardToken{
// those methods are called by the legacy contract
// and they must ensure msg.sender to be the contract address
function transferByLegacy(address from, address to, uint value) public;
function transferFromByLegacy(address sender, address from, address spender, uint value) public;
function approveByLegacy(address from, address spender, uint value) public;
}
contract TetherToken is Pausable, StandardToken, BlackList {
string public name;
string public symbol;
uint public decimals;
address public upgradedAddress;
bool public deprecated;
// The contract can be initialized with a number of tokens
// All the tokens are deposited to the owner address
//
// @param _balance Initial supply of the contract
// @param _name Token Name
// @param _symbol Token symbol
// @param _decimals Token decimals
function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
_totalSupply = _initialSupply;
name = _name;
symbol = _symbol;
decimals = _decimals;
balances[owner] = _initialSupply;
deprecated = false;
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function transfer(address _to, uint _value) public whenNotPaused {
require(!isBlackListed[msg.sender]);
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
} else {
return super.transfer(_to, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
require(!isBlackListed[_from]);
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
} else {
return super.transferFrom(_from, _to, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function balanceOf(address who) public constant returns (uint) {
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).balanceOf(who);
} else {
return super.balanceOf(who);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
} else {
return super.approve(_spender, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function allowance(address _owner, address _spender) public constant returns (uint remaining) {
if (deprecated) {
return StandardToken(upgradedAddress).allowance(_owner, _spender);
} else {
return super.allowance(_owner, _spender);
}
}
// deprecate current contract in favour of a new one
function deprecate(address _upgradedAddress) public onlyOwner {
deprecated = true;
upgradedAddress = _upgradedAddress;
Deprecate(_upgradedAddress);
}
// deprecate current contract if favour of a new one
function totalSupply() public constant returns (uint) {
if (deprecated) {
return StandardToken(upgradedAddress).totalSupply();
} else {
return _totalSupply;
}
}
// Issue a new amount of tokens
// these tokens are deposited into the owner address
//
// @param _amount Number of tokens to be issued
function issue(uint amount) public onlyOwner {
require(_totalSupply + amount > _totalSupply);
require(balances[owner] + amount > balances[owner]);
balances[owner] += amount;
_totalSupply += amount;
Issue(amount);
}
// Redeem tokens.
// These tokens are withdrawn from the owner address
// if the balance must be enough to cover the redeem
// or the call will fail.
// @param _amount Number of tokens to be issued
function redeem(uint amount) public onlyOwner {
require(_totalSupply >= amount);
require(balances[owner] >= amount);
_totalSupply -= amount;
balances[owner] -= amount;
Redeem(amount);
}
function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
// Ensure transparency by hardcoding limit beyond which fees can never be added
require(newBasisPoints < 20);
require(newMaxFee < 50);
basisPointsRate = newBasisPoints;
maximumFee = newMaxFee.mul(10**decimals);
Params(basisPointsRate, maximumFee);
}
// Called when new token are issued
event Issue(uint amount);
// Called when tokens are redeemed
event Redeem(uint amount);
// Called when contract is deprecated
event Deprecate(address newAddress);
// Called if contract ever adds fees
event Params(uint feeBasisPoints, uint maxFee);
}File 3 of 3: AmbirePaymaster
// 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;
}