Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AA-173: Remove simulation and view-only methods from the EntryPoint contract #321

Merged
merged 42 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ebbf21a
Makes comments and formatting consistent
gigamesh Mar 3, 2023
64cbdf6
@inheritdoc IEntryPoint
gigamesh Mar 4, 2023
d43bc94
Reverts contracts/test/TestHelpers.sol
gigamesh Mar 6, 2023
105d122
fix: entrypoint does not revert even first postOp reverts with short …
leekt May 30, 2023
3a6b0a5
lint
leekt May 30, 2023
66688cb
gas-report
leekt May 30, 2023
b56ef7b
Merge branch 'next_v0.7' into gigamesh/comments
forshtat Jun 25, 2023
74ed53b
Update IEntryPoint.sol - add missing 'INonceManager'
forshtat Jun 25, 2023
649daf8
Update EntryPoint.sol - fix lint errors
forshtat Jun 25, 2023
aa20d5d
Update gas-checker.txt manually
forshtat Jun 25, 2023
e3111fe
Update gas-checker.txt - add newline for the 'diff'
forshtat Jun 25, 2023
10f0502
Merge pull request #235 from gigamesh/gigamesh/comments
forshtat Jun 25, 2023
6a5ce09
Merge branch 'next_v0.7' into develop
forshtat Jun 25, 2023
8116f81
Update gas-checker.txt manually
forshtat Jun 25, 2023
74a3c37
Merge pull request #293 from leekt/develop
forshtat Jun 25, 2023
c0dd3ff
WIP: add 'anvil' node to github actions; first test with state override
forshtat Aug 1, 2023
38c14be
Skip anvil-specific test when running against a hardhat node
forshtat Aug 1, 2023
9a9f380
WIP: move simulation-related tests to anvil and run with state override
forshtat Aug 1, 2023
1a701cc
Deploy EntryPointSimulations for Hardhat tests - temporary solution
forshtat Aug 2, 2023
5ecd0f4
Uncomment 'simulateHandleOp' test
forshtat Aug 2, 2023
bc69fe7
Update gas checks
forshtat Aug 2, 2023
d16f337
Mine and 'evm_revert' a call to "simulateValidation"
forshtat Aug 2, 2023
5b1fb1a
Remove also internal simulation-only functions
forshtat Aug 3, 2023
31dcc37
Fix: even with "state override" keep 'validateSenderAndPaymaster' revert
forshtat Aug 3, 2023
29d56f9
Update gas checks
forshtat Aug 3, 2023
856eb9b
Address PR comments
forshtat Aug 3, 2023
eb84c29
support coverage
drortirosh Aug 10, 2023
03eeb19
fix tests
drortirosh Aug 10, 2023
03c3b52
Remove 'test-anvil' step - we will wait for Hardhat to add state diff…
forshtat Aug 13, 2023
2d47f78
Merge branch 'develop' into separate_simulation_entry_point
forshtat Aug 15, 2023
d930552
Update 'gas-checker.txt'
forshtat Aug 15, 2023
99a74ce
Address PR comments
forshtat Aug 15, 2023
17e39df
Missed file
forshtat Aug 15, 2023
887bfab
verify that EntryPoint calls never cost less during simulation (#328)
drortirosh Aug 17, 2023
74dc7fb
Add comment that 'EntryPointSimulations' should not be deployed
forshtat Aug 21, 2023
4887d64
Merge branch 'develop' into separate_simulation_entry_point
forshtat Aug 21, 2023
e2f4703
waste some gas
drortirosh Aug 21, 2023
28e0fe7
use latest hardhat, with support for eth_call state-override
drortirosh Aug 28, 2023
c254faf
lints, coverage
drortirosh Aug 28, 2023
ac5a4c9
simulator should not be deployed
drortirosh Sep 4, 2023
4c8a7f1
use "simulateValidation" helper, with state-override
drortirosh Sep 6, 2023
3a6b270
update EIP for simulation
drortirosh Sep 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 6 additions & 143 deletions contracts/core/EntryPoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ 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);
SenderCreator private senderCreator = new SenderCreator();

// Marker for inner call revert on out of gas
bytes32 private constant INNER_OUT_OF_GAS = hex"deaddead";
Expand Down Expand Up @@ -74,7 +71,10 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
uint256 opIndex,
UserOperation calldata userOp,
UserOpInfo memory opInfo
) private returns (uint256 collected) {
)
internal
returns
(uint256 collected) {
uint256 preGas = gasleft();
bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset);

Expand Down Expand Up @@ -217,41 +217,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
_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.
Expand Down Expand Up @@ -364,64 +329,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
}
}

/// @inheritdoc IEntryPoint
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
);
}

/**
* Get the required prefunded gas fee amount for an operation.
* @param mUserOp - The user operation in memory.
Expand Down Expand Up @@ -482,50 +389,6 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
revert SenderAddressResult(sender);
}

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("");
}

/**
* Call account.validateUserOp.
* Revert (with FailedOp) in case validateUserOp reverts, or account didn't send required prefund.
Expand Down Expand Up @@ -707,7 +570,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,
UserOperation calldata userOp,
UserOpInfo memory outOpInfo
)
private
internal
returns (uint256 validationData, uint256 paymasterValidationData)
{
uint256 preGas = gasleft();
Expand Down
185 changes: 185 additions & 0 deletions contracts/core/EntryPointSimulations.sol
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);
}
}
2 changes: 1 addition & 1 deletion contracts/core/StakeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ abstract contract StakeManager is IStakeManager {
* Add to the deposit of the given account.
* @param account - The account to add to.
*/
function depositTo(address account) public payable {
function depositTo(address account) public virtual payable {
_incrementDeposit(account, msg.value);
DepositInfo storage info = deposits[account];
emit Deposited(account, info.deposit);
Expand Down
Loading