//Imported from alto contracts //https://github.com/pimlicolabs/alto/blob/main/contracts/src/PimlicoEntryPointSimulations/EntryPointSimulations.sol //Itself was imported from eth-infinitism (main change is alto makes the contract deployable) //https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPointSimulations.sol // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.20; /* solhint-disable avoid-low-level-calls */ /* solhint-disable no-inline-assembly */ import "@account-abstraction/contracts/core/EntryPoint.sol"; import "./IEntryPointSimulations.sol"; /* * This contract inherits the EntryPoint and extends it with the view-only methods that are executed by * the bundler in order to check UserOperation validity and estimate its gas consumption. * This contract should never be deployed on-chain and is only used as a parameter for the "eth_call" request. * * Edit (Leo Vigna): Actually it SHOULD be deployed now that EntryPointV07 is designed for modular gas estimations without the need for * state overrides thanks to the addition of `delegateAndRevert` to the EntryPoint contract. The change below is from the * alto bundler contract but comments were not updated to reflect this. * Also see bottom section ("What's new v0.7") in this article for more info * https://blog.smart-contracts-developer.com/entrypoint-v0-7-0-the-new-era-of-account-abstraction-854f9f82912e */ contract EntryPointSimulations is EntryPoint, IEntryPointSimulations { // solhint-disable-next-line var-name-mixedcase AggregatorStakeInfo private NOT_AGGREGATED = AggregatorStakeInfo(address(0), StakeInfo(0, 0)); SenderCreator private _senderCreator; function initSenderCreator() internal virtual { //this is the address of the first contract created with CREATE by this address. address createdObj = address(uint160(uint256(keccak256(abi.encodePacked(hex"d694", address(this), hex"01"))))); _senderCreator = SenderCreator(createdObj); } function senderCreator() internal view virtual override returns (SenderCreator) { // return the same senderCreator as real EntryPoint. // this call is slightly (100) more expensive than EntryPoint's access to immutable member return _senderCreator; } /** * simulation contract should not be deployed, and specifically, accounts should not trust * it as entrypoint, since the simulation functions don't check the signatures * * Edit (Leo Vigna): Actually it SHOULD be deployed now that EntryPointV07 is designed for modular gas estimations without the need for * state overrides thanks to the addition of `delegateAndRevert` to the EntryPoint contract. The change below is from the * alto bundler contract but comments were not updated to reflect this. * Also see bottom section ("What's new v0.7") in this article for more info * https://blog.smart-contracts-developer.com/entrypoint-v0-7-0-the-new-era-of-account-abstraction-854f9f82912e */ constructor() { // require(block.number < 100, "should not be deployed"); } /// @inheritdoc IEntryPointSimulations function simulateValidation(PackedUserOperation calldata userOp) external returns (ValidationResult memory) { UserOpInfo memory outOpInfo; _simulationOnlyValidations(userOp); (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, userOp, outOpInfo); _validateAccountAndPaymasterValidationData(0, validationData, paymasterValidationData, address(0)); 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); } address aggregator = address(uint160(validationData)); ReturnInfo memory returnInfo = ReturnInfo( outOpInfo.preOpGas, outOpInfo.prefund, validationData, paymasterValidationData, getMemoryBytesFromOffset(outOpInfo.contextOffset) ); AggregatorStakeInfo memory aggregatorInfo = NOT_AGGREGATED; if (uint160(aggregator) != SIG_VALIDATION_SUCCESS && uint160(aggregator) != SIG_VALIDATION_FAILED) { aggregatorInfo = AggregatorStakeInfo(aggregator, _getStakeInfo(aggregator)); } return ValidationResult(returnInfo, senderInfo, factoryInfo, paymasterInfo, aggregatorInfo); } function simulateCallData( PackedUserOperation calldata op, address target, bytes calldata targetCallData ) external returns (TargetCallResult memory) { UserOpInfo memory opInfo; _simulationOnlyValidations(op); _validatePrepayment(0, op, opInfo); bool targetSuccess; bytes memory targetResult; uint256 usedGas; if (target != address(0)) { uint256 remainingGas = gasleft(); (targetSuccess, targetResult) = target.call(targetCallData); usedGas = remainingGas - gasleft(); } return TargetCallResult(usedGas, targetSuccess, targetResult); } /// @inheritdoc IEntryPointSimulations function simulateHandleOp(PackedUserOperation calldata op) external nonReentrant returns (ExecutionResult memory) { UserOpInfo memory opInfo; _simulationOnlyValidations(op); (uint256 validationData, uint256 paymasterValidationData) = _validatePrepayment(0, op, opInfo); uint256 paid = _executeUserOp(0, op, opInfo); return ExecutionResult(opInfo.preOpGas, paid, validationData, paymasterValidationData, false, "0x"); } function _simulationOnlyValidations(PackedUserOperation calldata userOp) internal { //initialize senderCreator(). we can't rely on constructor initSenderCreator(); string memory revertReason = _validateSenderAndPaymaster( userOp.initCode, userOp.sender, userOp.paymasterAndData ); // solhint-disable-next-line no-empty-blocks 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. * @param initCode - The smart account constructor code. * @param sender - The sender address. * @param paymasterAndData - The paymaster address (followed by other params, ignored by this method) */ function _validateSenderAndPaymaster( bytes calldata initCode, address sender, bytes calldata paymasterAndData ) internal view returns (string memory) { if (initCode.length == 0 && sender.code.length == 0) { // it would revert anyway. but give a meaningful message return ("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. return ("AA30 paymaster not deployed"); } } // always revert return (""); } //make sure depositTo cost is more than normal EntryPoint's cost, // to mitigate DoS vector on the bundler // empiric test showed that without this wrapper, simulation depositTo costs less.. function depositTo(address account) public payable override(IStakeManager, StakeManager) { unchecked { // silly code, to waste some gas to make sure depositTo is always little more // expensive than on-chain call uint256 x = 1; while (x < 5) { x++; } StakeManager.depositTo(account); } } }