Created
October 2, 2024 02:33
-
-
Save mingderwang/95f4bac6391604b140ebec28079007a7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
** Account-Abstraction (EIP-4337) singleton EntryPoint implementation. | |
** Only one instance required on each chain. | |
**/ | |
// SPDX-License-Identifier: GPL-3.0 | |
pragma solidity ^0.8.12; | |
/* solhint-disable avoid-low-level-calls */ | |
/* solhint-disable no-inline-assembly */ | |
import "../interfaces/IAccount.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 "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | |
contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard { | |
using UserOperationLib for UserOperation; | |
SenderCreator private immutable senderCreator = new SenderCreator(); | |
// internal value used during simulation: need to query aggregator. | |
address private constant SIMULATE_FIND_AGGREGATOR = address(1); | |
// marker for inner call revert on out of gas | |
bytes32 private constant INNER_OUT_OF_GAS = hex'deaddead'; | |
uint256 private constant REVERT_REASON_MAX_LEN = 2048; | |
/** | |
* for simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value | |
* in case of signature failure, instead of revert. | |
*/ | |
uint256 public constant SIG_VALIDATION_FAILED = 1; | |
/** | |
* 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 op | |
* @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, UserOperation calldata userOp, UserOpInfo memory opInfo) private returns (uint256 collected) { | |
uint256 preGas = gasleft(); | |
bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset); | |
try this.innerHandleOp(userOp.callData, opInfo, context) returns (uint256 _actualGasCost) { | |
collected = _actualGasCost; | |
} catch { | |
bytes32 innerRevertCode; | |
assembly { | |
returndatacopy(0, 0, 32) | |
innerRevertCode := mload(0) | |
} | |
// handleOps was called with gas limit too low. abort entire bundle. | |
if (innerRevertCode == INNER_OUT_OF_GAS) { | |
//report paymaster, since if it is not deliberately caused by the bundler, | |
// it must be a revert caused by paymaster. | |
revert FailedOp(opIndex, "AA95 out of gas"); | |
} | |
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas; | |
collected = _handlePostOp(opIndex, IPaymaster.PostOpMode.postOpReverted, opInfo, context, actualGas); | |
} | |
} | |
/** | |
* 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(UserOperation[] 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); | |
} //unchecked | |
} | |
/** | |
* 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 | |
) public nonReentrant { | |
uint256 opasLen = opsPerAggregator.length; | |
uint256 totalOps = 0; | |
for (uint256 i = 0; i < opasLen; i++) { | |
UserOpsPerAggregator calldata opa = opsPerAggregator[i]; | |
UserOperation[] 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); | |
emit BeforeExecution(); | |
uint256 opIndex = 0; | |
for (uint256 a = 0; a < opasLen; a++) { | |
UserOpsPerAggregator calldata opa = opsPerAggregator[a]; | |
UserOperation[] 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++; | |
} | |
} | |
uint256 collected = 0; | |
opIndex = 0; | |
for (uint256 a = 0; a < opasLen; a++) { | |
UserOpsPerAggregator calldata opa = opsPerAggregator[a]; | |
emit SignatureAggregatorChanged(address(opa.aggregator)); | |
UserOperation[] 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); | |
} | |
/// @inheritdoc IEntryPoint | |
function simulateHandleOp(UserOperation calldata op, address target, bytes calldata targetCallData) external override { | |
UserOpInfo memory opInfo; | |
_simulationOnlyValidations(op); | |
(uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, op, opInfo); | |
ValidationData memory data = _intersectTimeRange(validationData, paymasterValidationData); | |
numberMarker(); | |
uint256 paid = _executeUserOp(0, op, opInfo); | |
numberMarker(); | |
bool targetSuccess; | |
bytes memory targetResult; | |
if (target != address(0)) { | |
(targetSuccess, targetResult) = target.call(targetCallData); | |
} | |
revert ExecutionResult(opInfo.preOpGas, paid, data.validAfter, data.validUntil, targetSuccess, targetResult); | |
} | |
// A memory copy of UserOp static fields only. | |
// Excluding: callData, initCode and signature. Replacing paymasterAndData with paymaster. | |
struct MemoryUserOp { | |
address sender; | |
uint256 nonce; | |
uint256 callGasLimit; | |
uint256 verificationGasLimit; | |
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. | |
*/ | |
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; | |
uint callGasLimit = mUserOp.callGasLimit; | |
unchecked { | |
// handleOps was called with gas limit too low. abort entire bundle. | |
if (gasleft() < callGasLimit + mUserOp.verificationGasLimit + 5000) { | |
assembly { | |
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; | |
//note: opIndex is ignored (relevant only if mode==postOpReverted, which is only possible outside of innerHandleOp) | |
return _handlePostOp(0, mode, opInfo, context, actualGas); | |
} | |
} | |
/** | |
* 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. | |
*/ | |
function getUserOpHash(UserOperation 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. | |
*/ | |
function _copyUserOpToMemory(UserOperation calldata userOp, MemoryUserOp memory mUserOp) internal pure { | |
mUserOp.sender = userOp.sender; | |
mUserOp.nonce = userOp.nonce; | |
mUserOp.callGasLimit = userOp.callGasLimit; | |
mUserOp.verificationGasLimit = userOp.verificationGasLimit; | |
mUserOp.preVerificationGas = userOp.preVerificationGas; | |
mUserOp.maxFeePerGas = userOp.maxFeePerGas; | |
mUserOp.maxPriorityFeePerGas = userOp.maxPriorityFeePerGas; | |
bytes calldata paymasterAndData = userOp.paymasterAndData; | |
if (paymasterAndData.length > 0) { | |
require(paymasterAndData.length >= 20, "AA93 invalid paymasterAndData"); | |
mUserOp.paymaster = address(bytes20(paymasterAndData[: 20])); | |
} else { | |
mUserOp.paymaster = address(0); | |
} | |
} | |
/** | |
* Simulate a call to account.validateUserOp and paymaster.validatePaymasterUserOp. | |
* @dev this method always revert. Successful result is ValidationResult error. other errors are failures. | |
* @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage outside the account's data. | |
* @param userOp the user operation to validate. | |
*/ | |
function simulateValidation(UserOperation calldata userOp) external { | |
UserOpInfo memory outOpInfo; | |
_simulationOnlyValidations(userOp); | |
(uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, userOp, outOpInfo); | |
StakeInfo memory paymasterInfo = _getStakeInfo(outOpInfo.mUserOp.paymaster); | |
StakeInfo memory senderInfo = _getStakeInfo(outOpInfo.mUserOp.sender); | |
StakeInfo memory factoryInfo; | |
{ | |
bytes calldata initCode = userOp.initCode; | |
address factory = initCode.length >= 20 ? address(bytes20(initCode[0 : 20])) : address(0); | |
factoryInfo = _getStakeInfo(factory); | |
} | |
ValidationData memory data = _intersectTimeRange(validationData, paymasterValidationData); | |
address aggregator = data.aggregator; | |
bool sigFailed = aggregator == address(1); | |
ReturnInfo memory returnInfo = ReturnInfo(outOpInfo.preOpGas, outOpInfo.prefund, | |
sigFailed, data.validAfter, data.validUntil, getMemoryBytesFromOffset(outOpInfo.contextOffset)); | |
if (aggregator != address(0) && aggregator != address(1)) { | |
AggregatorStakeInfo memory aggregatorInfo = AggregatorStakeInfo(aggregator, _getStakeInfo(aggregator)); | |
revert ValidationResultWithAggregation(returnInfo, senderInfo, factoryInfo, paymasterInfo, aggregatorInfo); | |
} | |
revert ValidationResult(returnInfo, senderInfo, factoryInfo, paymasterInfo); | |
} | |
function _getRequiredPrefund(MemoryUserOp memory mUserOp) internal pure returns (uint256 requiredPrefund) { | |
unchecked { | |
//when using a Paymaster, the verificationGasLimit is used also to as a limit for the postOp call. | |
// our security model might call postOp eventually twice | |
uint256 mul = mUserOp.paymaster != address(0) ? 3 : 1; | |
uint256 requiredGas = mUserOp.callGasLimit + mUserOp.verificationGasLimit * mul + mUserOp.preVerificationGas; | |
requiredPrefund = requiredGas * mUserOp.maxFeePerGas; | |
} | |
} | |
// create the sender's contract if needed. | |
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); | |
} | |
} | |
/** | |
* 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 calldata initCode) public { | |
address sender = senderCreator.createSender(initCode); | |
revert SenderAddressResult(sender); | |
} | |
function _simulationOnlyValidations(UserOperation calldata userOp) internal view { | |
// solhint-disable-next-line no-empty-blocks | |
try this._validateSenderAndPaymaster(userOp.initCode, userOp.sender, userOp.paymasterAndData) {} | |
catch Error(string memory revertReason) { | |
if (bytes(revertReason).length != 0) { | |
revert FailedOp(0, revertReason); | |
} | |
} | |
} | |
/** | |
* Called only during simulation. | |
* This function always reverts to prevent warm/cold storage differentiation in simulation vs execution. | |
*/ | |
function _validateSenderAndPaymaster(bytes calldata initCode, address sender, bytes calldata paymasterAndData) external view { | |
if (initCode.length == 0 && sender.code.length == 0) { | |
// it would revert anyway. but give a meaningful message | |
revert("AA20 account not deployed"); | |
} | |
if (paymasterAndData.length >= 20) { | |
address paymaster = address(bytes20(paymasterAndData[0 : 20])); | |
if (paymaster.code.length == 0) { | |
// it would revert anyway. but give a meaningful message | |
revert("AA30 paymaster not deployed"); | |
} | |
} | |
// always revert | |
revert(""); | |
} | |
/** | |
* call account.validateUserOp. | |
* revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund. | |
* decrement account's deposit if needed | |
*/ | |
function _validateAccountPrepayment(uint256 opIndex, UserOperation calldata op, UserOpInfo memory opInfo, uint256 requiredPrefund) | |
internal returns (uint256 gasUsedByValidateAccountPrepayment, uint256 validationData) { | |
unchecked { | |
uint256 preGas = gasleft(); | |
MemoryUserOp memory mUserOp = opInfo.mUserOp; | |
address sender = mUserOp.sender; | |
_createSenderIfNeeded(opIndex, opInfo, op.initCode); | |
address paymaster = mUserOp.paymaster; | |
numberMarker(); | |
uint256 missingAccountFunds = 0; | |
if (paymaster == address(0)) { | |
uint256 bal = balanceOf(sender); | |
missingAccountFunds = bal > requiredPrefund ? 0 : requiredPrefund - bal; | |
} | |
try IAccount(sender).validateUserOp{gas : mUserOp.verificationGasLimit}(op, opInfo.userOpHash, missingAccountFunds) | |
returns (uint256 _validationData) { | |
validationData = _validationData; | |
} catch Error(string memory revertReason) { | |
revert FailedOp(opIndex, string.concat("AA23 reverted: ", revertReason)); | |
} catch { | |
revert FailedOp(opIndex, "AA23 reverted (or OOG)"); | |
} | |
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 = uint112(deposit - requiredPrefund); | |
} | |
gasUsedByValidateAccountPrepayment = preGas - gasleft(); | |
} | |
} | |
/** | |
* 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 | |
*/ | |
function _validatePaymasterPrepayment(uint256 opIndex, UserOperation calldata op, UserOpInfo memory opInfo, uint256 requiredPreFund, uint256 gasUsedByValidateAccountPrepayment) | |
internal returns (bytes memory context, uint256 validationData) { | |
unchecked { | |
MemoryUserOp memory mUserOp = opInfo.mUserOp; | |
uint256 verificationGasLimit = mUserOp.verificationGasLimit; | |
require(verificationGasLimit > gasUsedByValidateAccountPrepayment, "AA41 too little verificationGas"); | |
uint256 gas = verificationGasLimit - gasUsedByValidateAccountPrepayment; | |
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 = uint112(deposit - requiredPreFund); | |
try IPaymaster(paymaster).validatePaymasterUserOp{gas : gas}(op, opInfo.userOpHash, requiredPreFund) returns (bytes memory _context, uint256 _validationData){ | |
context = _context; | |
validationData = _validationData; | |
} catch Error(string memory revertReason) { | |
revert FailedOp(opIndex, string.concat("AA33 reverted: ", revertReason)); | |
} catch { | |
revert FailedOp(opIndex, "AA33 reverted (or OOG)"); | |
} | |
} | |
} | |
/** | |
* revert if either account validationData or paymaster validationData is expired | |
*/ | |
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"); | |
} | |
} | |
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). | |
* 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, UserOperation calldata userOp, UserOpInfo memory outOpInfo) | |
private 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 maxGasValues = mUserOp.preVerificationGas | mUserOp.verificationGasLimit | mUserOp.callGasLimit | | |
userOp.maxFeePerGas | userOp.maxPriorityFeePerGas; | |
require(maxGasValues <= type(uint120).max, "AA94 gas values overflow"); | |
uint256 gasUsedByValidateAccountPrepayment; | |
(uint256 requiredPreFund) = _getRequiredPrefund(mUserOp); | |
(gasUsedByValidateAccountPrepayment, validationData) = _validateAccountPrepayment(opIndex, userOp, outOpInfo, requiredPreFund); | |
if (!_validateAndUpdateNonce(mUserOp.sender, mUserOp.nonce)) { | |
revert FailedOp(opIndex, "AA25 invalid account nonce"); | |
} | |
//a "marker" where account opcode validation is done and paymaster opcode validation is about to start | |
// (used only by off-chain simulateValidation) | |
numberMarker(); | |
bytes memory context; | |
if (mUserOp.paymaster != address(0)) { | |
(context, paymasterValidationData) = _validatePaymasterPrepayment(opIndex, userOp, outOpInfo, requiredPreFund, gasUsedByValidateAccountPrepayment); | |
} | |
unchecked { | |
uint256 gasUsed = preGas - gasleft(); | |
if (userOp.verificationGasLimit < gasUsed) { | |
revert FailedOp(opIndex, "AA40 over verificationGasLimit"); | |
} | |
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 opIndex index in the batch | |
* @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 _handlePostOp(uint256 opIndex, 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) { | |
IPaymaster(paymaster).postOp{gas : mUserOp.verificationGasLimit}(mode, context, actualGasCost); | |
} else { | |
// solhint-disable-next-line no-empty-blocks | |
try IPaymaster(paymaster).postOp{gas : mUserOp.verificationGasLimit}(mode, context, actualGasCost) {} | |
catch Error(string memory reason) { | |
revert FailedOp(opIndex, string.concat("AA50 postOp reverted: ", reason)); | |
} | |
catch { | |
revert FailedOp(opIndex, "AA50 postOp revert"); | |
} | |
} | |
} | |
} | |
actualGas += preGas - gasleft(); | |
actualGasCost = actualGas * gasPrice; | |
if (opInfo.prefund < actualGasCost) { | |
revert FailedOp(opIndex, "AA51 prefund below actualGasCost"); | |
} | |
uint256 refund = opInfo.prefund - actualGasCost; | |
_incrementDeposit(refundAddress, refund); | |
bool success = mode == IPaymaster.PostOpMode.opSucceeded; | |
emit UserOperationEvent(opInfo.userOpHash, mUserOp.sender, mUserOp.paymaster, mUserOp.nonce, 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 | |
*/ | |
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); | |
} | |
} | |
function min(uint256 a, uint256 b) internal pure returns (uint256) { | |
return a < b ? a : b; | |
} | |
function getOffsetOfMemoryBytes(bytes memory data) internal pure returns (uint256 offset) { | |
assembly {offset := data} | |
} | |
function getMemoryBytesFromOffset(uint256 offset) internal pure returns (bytes memory data) { | |
assembly {data := offset} | |
} | |
//place the NUMBER opcode in the code. | |
// this is used as a marker during simulation, as this OP is completely banned from the simulated code of the | |
// account and paymaster. | |
function numberMarker() internal view { | |
assembly {mstore(0, number())} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment