@@ -6,7 +6,6 @@ import { Math } from "openzeppelin-contracts/contracts/utils/math/Math.sol";
6
6
7
7
import "../types/SolverOperation.sol " ;
8
8
import "../types/UserOperation.sol " ;
9
- import "../types/LockTypes.sol " ;
10
9
import "../types/DAppOperation.sol " ;
11
10
import "../types/ConfigTypes.sol " ;
12
11
import "../types/ValidCalls.sol " ;
@@ -25,8 +24,17 @@ contract Simulator is AtlasErrors, AtlasConstants {
25
24
using AccountingMath for uint256 ;
26
25
using GasAccLib for SolverOperation[];
27
26
28
- uint256 internal constant _SIM_GAS_SUGGESTED_BUFFER = 30_000 ;
29
- uint256 internal constant _SIM_GAS_BEFORE_METACALL = 10_000 ;
27
+ // The approx gas used between the minGasLeft check and the Atlas metacall() from this Simulator contract.
28
+ uint256 internal constant _ERROR_CATCHER_GAS_BUFFER = 10_000 ;
29
+ // The approx gas used between the `simUserOperation()` or `simSolverCall()` entrypoint functions in this Simulator
30
+ // contract, and the minGasLeft check.
31
+ uint256 internal constant _SIM_ENTRYPOINT_GAS_BUFFER = 50_000 ;
32
+
33
+ // An approximation of (64 / 63) ^ 2. This is the scaling to apply to a min amount of gas, such that after 2
34
+ // external calls where only 63/64 of the gas is forwarded, there is still that original intended amount of gas
35
+ // left. Used in _errorCatcher() below.
36
+ uint256 internal constant _MIN_GAS_SCALING_FACTOR = 10_320 ; // 10_320 out of 10_000 = 1.032
37
+ uint256 internal constant _SCALE = AccountingMath._SCALE; // 10_000 = 100%
30
38
31
39
address public immutable deployer;
32
40
address public atlas;
@@ -49,6 +57,19 @@ contract Simulator is AtlasErrors, AtlasConstants {
49
57
public
50
58
view
51
59
returns (uint256 )
60
+ {
61
+ (uint256 metacallCalldataGas , uint256 metacallExecutionGas ) = _estimateMetacallGasLimit (userOp, solverOps);
62
+ return metacallCalldataGas + metacallExecutionGas;
63
+ }
64
+
65
+ /// @notice Internal function to estimate the calldata and execution gas for a metacall.
66
+ function _estimateMetacallGasLimit (
67
+ UserOperation calldata userOp ,
68
+ SolverOperation[] memory solverOps
69
+ )
70
+ internal
71
+ view
72
+ returns (uint256 metacallCalldataGas , uint256 metacallExecutionGas )
52
73
{
53
74
DAppConfig memory dConfig = IDAppControl (userOp.control).getDAppConfig (userOp);
54
75
address l2GasCalculator = IAtlas (atlas).L2_GAS_CALCULATOR ();
@@ -65,18 +86,19 @@ contract Simulator is AtlasErrors, AtlasConstants {
65
86
allSolversExecutionGas += Math.min (solverOps[i].gas, dConfig.solverGasLimit);
66
87
}
67
88
68
- uint256 metacallCalldataGas = (_SOLVER_OP_BASE_CALLDATA * solverOpsLen)
69
- + GasAccLib.calldataGas (solverDataLenSum, l2GasCalculator)
70
- + GasAccLib.metacallCalldataGas (nonSolverCalldataLength, l2GasCalculator);
89
+ // metacallCalldataGas is first item in return tuple
90
+ metacallCalldataGas = GasAccLib.calldataGas (
91
+ solverDataLenSum + (_SOLVER_OP_BASE_CALLDATA * solverOpsLen), l2GasCalculator
92
+ ) + GasAccLib.metacallCalldataGas (nonSolverCalldataLength, l2GasCalculator);
71
93
72
- uint256 metacallExecutionGas = _BASE_TX_GAS_USED + AccountingMath._FIXED_GAS_OFFSET + userOp.gas
73
- + dConfig.dappGasLimit + allSolversExecutionGas;
94
+ // metacallExecutionGas is second item in return tuple
95
+ metacallExecutionGas = _BASE_TX_GAS_USED + AccountingMath._FIXED_GAS_OFFSET + userOp.gas + dConfig.dappGasLimit
96
+ + allSolversExecutionGas;
74
97
98
+ // If exPostBids = true, add extra execution gas to account for bid-finding.
75
99
if (dConfig.callConfig.exPostBids ()) {
76
100
metacallExecutionGas += (solverOpsLen * _BID_FIND_OVERHEAD) + allSolversExecutionGas;
77
101
}
78
-
79
- return metacallCalldataGas + metacallExecutionGas;
80
102
}
81
103
82
104
/// @notice Returns an estimation of the max amount (in native token) a winning solver could be charged, given the
@@ -148,9 +170,10 @@ contract Simulator is AtlasErrors, AtlasConstants {
148
170
dAppOp.to = atlas;
149
171
dAppOp.control = userOp.control;
150
172
151
- uint256 estGasLim = estimateMetacallGasLimit (userOp, solverOps);
173
+ ( uint256 metacallCalldataGas , uint256 metacallExecutionGas ) = _estimateMetacallGasLimit (userOp, solverOps);
152
174
153
- (Result result , uint256 validCallsResult ) = _errorCatcher (userOp, solverOps, dAppOp, estGasLim);
175
+ (Result result , uint256 validCallsResult ) =
176
+ _errorCatcher (userOp, solverOps, dAppOp, metacallExecutionGas, metacallCalldataGas);
154
177
success = uint8 (result) > uint8 (Result.UserOpSimFail);
155
178
if (success) validCallsResult = uint256 (ValidCallsResult.Valid);
156
179
if (msg .value != 0 ) SafeTransferLib.safeTransferETH (msg .sender , msg .value );
@@ -171,9 +194,10 @@ contract Simulator is AtlasErrors, AtlasConstants {
171
194
SolverOperation[] memory solverOps = new SolverOperation [](1 );
172
195
solverOps[0 ] = solverOp;
173
196
174
- uint256 estGasLim = estimateMetacallGasLimit (userOp, solverOps);
197
+ ( uint256 metacallCalldataGas , uint256 metacallExecutionGas ) = _estimateMetacallGasLimit (userOp, solverOps);
175
198
176
- (Result result , uint256 solverOutcomeResult ) = _errorCatcher (userOp, solverOps, dAppOp, estGasLim);
199
+ (Result result , uint256 solverOutcomeResult ) =
200
+ _errorCatcher (userOp, solverOps, dAppOp, metacallExecutionGas, metacallCalldataGas);
177
201
success = result == Result.SimulationPassed;
178
202
if (success) solverOutcomeResult = 0 ; // discard additional error uint if solver stage was successful
179
203
if (msg .value != 0 ) SafeTransferLib.safeTransferETH (msg .sender , msg .value );
@@ -196,9 +220,10 @@ contract Simulator is AtlasErrors, AtlasConstants {
196
220
return (false , Result.Unknown, uint256 (type (SolverOutcome).max) + 1 );
197
221
}
198
222
199
- uint256 estGasLim = estimateMetacallGasLimit (userOp, solverOps);
223
+ ( uint256 metacallCalldataGas , uint256 metacallExecutionGas ) = _estimateMetacallGasLimit (userOp, solverOps);
200
224
201
- (Result result , uint256 solverOutcomeResult ) = _errorCatcher (userOp, solverOps, dAppOp, estGasLim);
225
+ (Result result , uint256 solverOutcomeResult ) =
226
+ _errorCatcher (userOp, solverOps, dAppOp, metacallExecutionGas, metacallCalldataGas);
202
227
success = result == Result.SimulationPassed;
203
228
if (success) solverOutcomeResult = 0 ; // discard additional error uint if solver stage was successful
204
229
if (msg .value != 0 ) SafeTransferLib.safeTransferETH (msg .sender , msg .value );
@@ -209,16 +234,33 @@ contract Simulator is AtlasErrors, AtlasConstants {
209
234
UserOperation memory userOp ,
210
235
SolverOperation[] memory solverOps ,
211
236
DAppOperation memory dAppOp ,
212
- uint256 estGasLimit
237
+ uint256 metacallExecutionGas ,
238
+ uint256 metacallCalldataGas
213
239
)
214
240
internal
215
241
returns (Result result , uint256 additionalErrorCode )
216
242
{
217
- if (gasleft () < estGasLimit + _SIM_GAS_BEFORE_METACALL) {
218
- revert InsufficientGasForMetacallSimulation (estGasLimit, estGasLimit + _SIM_GAS_SUGGESTED_BUFFER);
243
+ // Calculate the minimum gas left at this point, given that the Atlas `metacall()` should start with
244
+ // approximately `metacallExecutionGas`, and that there are still 2x external calls to be made before that
245
+ // point:
246
+ // - First in the `metacallSimulation()` call below.
247
+ // - Second when the `metacallSimulation()` function calls `metacall()` in Atlas.
248
+ //
249
+ // For each external call, a max of 63/64 of the gas left is forwarded.
250
+ // Therefore we should have at least [metacallExecutionGas * (64/63) ^ 2] at this point to ensure there will be
251
+ // enough gas at the start of the `metacall()`
252
+ uint256 minGasLeft = metacallExecutionGas * _MIN_GAS_SCALING_FACTOR / _SCALE + _ERROR_CATCHER_GAS_BUFFER;
253
+ uint256 gasLeft = gasleft ();
254
+
255
+ if (gasLeft < minGasLeft) {
256
+ revert InsufficientGasForMetacallSimulation (
257
+ gasLeft, // The gas left at this point that was insufficient to pass the check
258
+ metacallExecutionGas + metacallCalldataGas, // Suggested gas limit for a real metacall
259
+ _SIM_ENTRYPOINT_GAS_BUFFER + minGasLeft + metacallCalldataGas // Suggested gas limit for a sim call
260
+ );
219
261
}
220
262
221
- try this .metacallSimulation { value: userOp.value }(userOp, solverOps, dAppOp, estGasLimit ) {
263
+ try this .metacallSimulation { value: userOp.value }(userOp, solverOps, dAppOp, metacallExecutionGas ) {
222
264
revert Unreachable ();
223
265
} catch (bytes memory revertData ) {
224
266
bytes4 errorSwitch = bytes4 (revertData);
@@ -261,14 +303,20 @@ contract Simulator is AtlasErrors, AtlasConstants {
261
303
UserOperation calldata userOp ,
262
304
SolverOperation[] calldata solverOps ,
263
305
DAppOperation calldata dAppOp ,
264
- uint256 estGasLimit
306
+ uint256 metacallExecutionGas
265
307
)
266
308
external
267
309
payable
268
310
{
269
311
if (msg .sender != address (this )) revert InvalidEntryFunction ();
312
+
313
+ // In real Atlas metacalls, when the caller is an EOA, it should include the suggested calldata gas in the gas
314
+ // limit. However, in as this is a Simulator call, that calldata gas has already been deducted in the initial
315
+ // `simUserOperation()` or `simSolverCall()` call. As such, we set the metacall gas limit here to just the
316
+ // suggested execution gas.
317
+
270
318
bool auctionWon =
271
- IAtlas (atlas).metacall { value: msg .value , gas: estGasLimit }(userOp, solverOps, dAppOp, address (0 ));
319
+ IAtlas (atlas).metacall { value: msg .value , gas: metacallExecutionGas }(userOp, solverOps, dAppOp, address (0 ));
272
320
273
321
// If multipleSuccessfulSolvers = true, metacall always returns auctionWon = false, even if there were some
274
322
// successful solvers. So we always revert with SimulationPassed here if multipleSuccessfulSolvers = true.
0 commit comments