-
Notifications
You must be signed in to change notification settings - Fork 670
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AA-173: Remove simulation and view-only methods from the EntryPoint c…
…ontract (#321) * All simulation functions are in EntryPointSimulations contract * This contract is never deployed. it is applied as "state-override" when doing simulation with eth_call/debug_traceCall * update EIP to allowed calls back into the EntryPoint * make sure entryPoint methods callable from validation (deposit) always cost the same or more during simulation. Co-authored-by: Dror Tirosh <[email protected]>
- Loading branch information
Showing
20 changed files
with
934 additions
and
644 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.12; | ||
|
||
/* solhint-disable avoid-low-level-calls */ | ||
/* solhint-disable no-inline-assembly */ | ||
|
||
import "./EntryPoint.sol"; | ||
import "../interfaces/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. | ||
*/ | ||
contract EntryPointSimulations is EntryPoint, IEntryPointSimulations { | ||
// solhint-disable-next-line var-name-mixedcase | ||
AggregatorStakeInfo private NOT_AGGREGATED = AggregatorStakeInfo(address(0), StakeInfo(0, 0)); | ||
|
||
/** | ||
* simulation contract should not be deployed, and specifically, accounts should not trust | ||
* it as entrypoint, since the simulation functions don't check the signatures | ||
*/ | ||
constructor() { | ||
require(block.number < 100, "should not be deployed"); | ||
} | ||
|
||
/// @inheritdoc IEntryPointSimulations | ||
function simulateValidation( | ||
UserOperation calldata userOp | ||
) | ||
external | ||
returns ( | ||
ValidationResult memory | ||
){ | ||
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) | ||
); | ||
|
||
AggregatorStakeInfo memory aggregatorInfo = NOT_AGGREGATED; | ||
if (aggregator != address(0) && aggregator != address(1)) { | ||
aggregatorInfo = AggregatorStakeInfo( | ||
aggregator, | ||
_getStakeInfo(aggregator) | ||
); | ||
} | ||
return ValidationResult( | ||
returnInfo, | ||
senderInfo, | ||
factoryInfo, | ||
paymasterInfo, | ||
aggregatorInfo | ||
); | ||
} | ||
|
||
/// @inheritdoc IEntryPointSimulations | ||
function simulateHandleOp( | ||
UserOperation calldata op, | ||
address target, | ||
bytes calldata targetCallData | ||
) | ||
external nonReentrant | ||
returns ( | ||
ExecutionResult memory | ||
){ | ||
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); | ||
} | ||
return ExecutionResult( | ||
opInfo.preOpGas, | ||
paid, | ||
data.validAfter, | ||
data.validUntil, | ||
targetSuccess, | ||
targetResult | ||
); | ||
} | ||
|
||
function _simulationOnlyValidations( | ||
UserOperation calldata userOp | ||
) | ||
internal | ||
view | ||
{ | ||
try | ||
this._validateSenderAndPaymaster( | ||
userOp.initCode, | ||
userOp.sender, | ||
userOp.paymasterAndData | ||
) | ||
// solhint-disable-next-line no-empty-blocks | ||
{} 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. | ||
* @param initCode - The smart account constructor code. | ||
* @param sender - The sender address. | ||
* @param paymasterAndData - The paymaster address followed by the token address to use. | ||
*/ | ||
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(""); | ||
} | ||
|
||
//make sure depositTo cost is more than normal EntryPoint's cost. | ||
// empiric test showed that without this wrapper, simulation depositTo costs less.. | ||
function depositTo(address account) public override(IStakeManager, StakeManager) payable { | ||
uint x; | ||
assembly { | ||
//some silly code to waste ~200 gas | ||
x := exp(mload(0),100) | ||
} | ||
if (x == 123) { | ||
return; | ||
} | ||
StakeManager.depositTo(account); | ||
} | ||
} |
This file contains 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
Oops, something went wrong.