@@ -3,6 +3,8 @@ pragma solidity 0.8.28;
3
3
4
4
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol " ;
5
5
import { LibSort } from "solady/utils/LibSort.sol " ;
6
+ import { Math } from "openzeppelin-contracts/contracts/utils/math/Math.sol " ;
7
+ import { SafeCast } from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol " ;
6
8
7
9
import { Escrow } from "./Escrow.sol " ;
8
10
import { Factory } from "./Factory.sol " ;
@@ -20,10 +22,11 @@ import { GasAccLib, GasLedger } from "../libraries/GasAccLib.sol";
20
22
import { IL2GasCalculator } from "../interfaces/IL2GasCalculator.sol " ;
21
23
import { IDAppControl } from "../interfaces/IDAppControl.sol " ;
22
24
23
- /// @title Atlas V1.5
25
+ /// @title Atlas V1.6
24
26
/// @author FastLane Labs
25
27
/// @notice The Execution Abstraction protocol.
26
28
contract Atlas is Escrow , Factory {
29
+ using SafeCast for uint256 ;
27
30
using CallBits for uint32 ;
28
31
using SafetyBits for Context;
29
32
using GasAccLib for uint256 ;
@@ -32,22 +35,13 @@ contract Atlas is Escrow, Factory {
32
35
constructor (
33
36
uint256 escrowDuration ,
34
37
uint256 atlasSurchargeRate ,
35
- uint256 bundlerSurchargeRate ,
36
38
address verification ,
37
39
address simulator ,
38
40
address initialSurchargeRecipient ,
39
41
address l2GasCalculator ,
40
42
address factoryLib
41
43
)
42
- Escrow (
43
- escrowDuration,
44
- atlasSurchargeRate,
45
- bundlerSurchargeRate,
46
- verification,
47
- simulator,
48
- initialSurchargeRecipient,
49
- l2GasCalculator
50
- )
44
+ Escrow (escrowDuration, atlasSurchargeRate, verification, simulator, initialSurchargeRecipient, l2GasCalculator)
51
45
Factory (factoryLib)
52
46
{ }
53
47
@@ -123,8 +117,23 @@ contract Atlas is Escrow, Factory {
123
117
// Initialize the environment lock and accounting values
124
118
_setEnvironmentLock (_dConfig, _executionEnvironment);
125
119
126
- // _gasMarker - _bidFindOverhead = estimated winning solver gas liability (not charged for bid-find gas)
127
- _initializeAccountingValues (_gasMarker - _bidFindOverhead, _allSolversGasLimit);
120
+ // If in `multipleSuccessfulSolvers` mode, solvers are only liable for their own (C + E) gas costs, even if they
121
+ // execute successfully. In this case we set `remainingMaxGas` and `unreachedSolverGas` to the same value - the
122
+ // sum of all solvers' (C + E) gas limits. Then, `solverGasLiability()` will report just the current solver's
123
+ // gas costs with surcharges.
124
+ // In all other cases, `remainingMaxGas` includes non-solver gas limits and overheads that the winning solver
125
+ // may be liable for. In `exPostBids` mode, we also subtract the gas limit of the bid-finding solverOp
126
+ // iterations, as that gas will be written off (winning solver not liable for them).
127
+ uint256 _initialRemainingMaxGas =
128
+ _dConfig.callConfig.multipleSuccessfulSolvers () ? _allSolversGasLimit : _gasMarker - _bidFindOverhead;
129
+
130
+ // `userOp.bundlerSurchargeRate` is checked against the value set in the DAppControl in `validateCalls()` above,
131
+ // so it is safe to use here.
132
+ _initializeAccountingValues ({
133
+ initialRemainingMaxGas: _initialRemainingMaxGas,
134
+ allSolverOpsGas: _allSolversGasLimit,
135
+ bundlerSurchargeRate: userOp.bundlerSurchargeRate
136
+ });
128
137
129
138
// Calculate `execute` gas limit such that it can fail due to an OOG error caused by any of the hook calls, and
130
139
// the metacall will still have enough gas to gracefully finish and return, storing any nonces required.
@@ -152,9 +161,15 @@ contract Atlas is Escrow, Factory {
152
161
);
153
162
}
154
163
155
- // Gas Refund to sender only if execution is successful
156
- (uint256 _ethPaidToBundler , uint256 _netGasSurcharge ) =
157
- _settle (ctx, _gL, _gasMarker, gasRefundBeneficiary, _unreachedCalldataValuePaid);
164
+ // Gas Refund to sender only if execution is successful, or if multipleSuccessfulSolvers
165
+ (uint256 _ethPaidToBundler , uint256 _netGasSurcharge ) = _settle (
166
+ ctx,
167
+ _gL,
168
+ _gasMarker,
169
+ gasRefundBeneficiary,
170
+ _unreachedCalldataValuePaid,
171
+ _dConfig.callConfig.multipleSuccessfulSolvers ()
172
+ );
158
173
159
174
auctionWon = ctx.solverSuccessful;
160
175
emit MetacallResult (msg .sender , userOp.from, auctionWon, _ethPaidToBundler, _netGasSurcharge);
@@ -173,7 +188,8 @@ contract Atlas is Escrow, Factory {
173
188
emit MetacallResult (msg .sender , userOp.from, false , 0 , 0 );
174
189
}
175
190
176
- // The environment lock is explicitly released here to allow multiple metacalls in a single transaction.
191
+ // The environment lock is explicitly released here to allow multiple (sequential, not nested) metacalls in a
192
+ // single transaction.
177
193
_releaseLock ();
178
194
}
179
195
@@ -258,6 +274,7 @@ contract Atlas is Escrow, Factory {
258
274
259
275
uint256 [] memory _bidsAndIndices = new uint256 [](solverOpsLength);
260
276
uint256 _bidAmountFound;
277
+ uint256 _solverExecutionGas;
261
278
uint256 _bidsAndIndicesLastIndex = solverOpsLength - 1 ; // Start from the last index
262
279
263
280
// Get a snapshot of the GasLedger from transient storage, to reset to after bid-finding below
@@ -287,7 +304,9 @@ contract Atlas is Escrow, Factory {
287
304
// remainingMaxGas separately here. This decrease does not include calldata gas as solvers are not charged
288
305
// for calldata gas in exPostBids mode.
289
306
GasLedger memory _gL = t_gasLedger.toGasLedger ();
290
- _gL.remainingMaxGas -= uint48 (dConfig.solverGasLimit);
307
+ // Deduct solverOp.gas (with a ceiling of dConfig.solverGasLimit) from remainingMaxGas
308
+ _solverExecutionGas = Math.min (solverOps[i].gas, dConfig.solverGasLimit);
309
+ _gL.remainingMaxGas -= _solverExecutionGas.toUint40 ();
291
310
t_gasLedger = _gL.pack ();
292
311
293
312
// skip zero and overflow bid's
@@ -378,14 +397,29 @@ contract Atlas is Escrow, Factory {
378
397
379
398
SolverOperation calldata solverOp = solverOps[ctx.solverIndex];
380
399
381
- _bidAmount = _executeSolverOperation (
400
+ // if multipleSuccessfulSolvers = true, solver bids are summed here. Otherwise, 0 bids are returned on
401
+ // solverOp failure, and only the first successful solver's bid is added to `_bidAmount`.
402
+ _bidAmount += _executeSolverOperation (
382
403
ctx, dConfig, userOp, solverOp, solverOp.bidAmount, _gasWaterMark, false , returnData
383
404
);
384
405
406
+ // If a winning solver is found, stop iterating through the solverOps and return the winning bid
385
407
if (ctx.solverSuccessful) {
386
408
return _bidAmount;
387
409
}
388
410
}
411
+
412
+ // If no winning solver, but multipleSuccessfulSolvers is true, return the sum of solver bid amounts
413
+ if (dConfig.callConfig.multipleSuccessfulSolvers ()) {
414
+ // Considered a fail for simulation purposes when only one solverOp in the metacall, and it fails. If more
415
+ // than 1 solverOp, any of them could fail and simulation could still be successful.
416
+ if (ctx.isSimulation && solverOpsLen == 1 && ctx.solverOutcome != 0 ) {
417
+ revert SolverSimFail (uint256 (ctx.solverOutcome));
418
+ }
419
+
420
+ return _bidAmount;
421
+ }
422
+
389
423
if (ctx.isSimulation) revert SolverSimFail (uint256 (ctx.solverOutcome));
390
424
if (dConfig.callConfig.needsFulfillment ()) revert UserNotFulfilled ();
391
425
return 0 ;
0 commit comments