Skip to content

Commit 615542b

Browse files
Merge pull request #478 from FastLane-Labs/atlas-v1.6-audit
feat: Atlas v1.6
2 parents ff27f75 + 5b07082 commit 615542b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2125
-432
lines changed

audits/Atlas v1.6 - Spearbit.pdf

522 KB
Binary file not shown.

deployments.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@
6262
"L2_GAS_CALCULATOR": "0x3b7B38362bB7E2F000Cd2432343F3483F785F435"
6363
},
6464
"BASE": {
65-
"ATLAS": "0x3efbaBE0ee916A4677D281c417E895a3e7411Ac2",
66-
"ATLAS_VERIFICATION": "0x16839b7Bb46136B204Bcb7eA71c9226952c02b34",
67-
"SIMULATOR": "0xa55051bd82eFeA1dD487875C84fE9c016859659B",
68-
"SORTER": "0xD72D821dA82964c0546a5501347a3959808E072f",
65+
"ATLAS": "0xD7Bc856FF47E5DC3f569d7a1D3948056bd5EA9fC",
66+
"ATLAS_VERIFICATION": "0xbd70aA1A173472e39b412C5E95646231b690649D",
67+
"SIMULATOR": "0xa9aF59fA414772A1f6ca8EDe0223b6e2b0bB7FfD",
68+
"SORTER": "0xd9897bafc9a0408242077555788aB8949F1eFD3B",
6969
"L2_GAS_CALCULATOR": "0xf9436C4b1353D5B411AD5bb65B9826f34737BbC7"
7070
},
7171
"BERACHAIN": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "atlas",
3-
"version": "1.5.0",
3+
"version": "1.6.0",
44
"repository": "https://github.com/Polygon-Fast-Lane/atlas",
55
"author": "FastLane",
66
"scripts": {

script/base/deploy-base.s.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ contract DeployBaseScript is Script {
7878
uint256 chainId = block.chainid;
7979
if (chainId == 137 || chainId == 80_002) {
8080
// POLYGON and AMOY
81-
atlasSurchargeRate = 500_000; // 5%
82-
bundlerSurchargeRate = 2_000_000; // 20%
81+
atlasSurchargeRate = 500; // 5%
82+
bundlerSurchargeRate = 2000; // 20%
8383
} else {
8484
// Default - for all other chains
85-
atlasSurchargeRate = 1_000_000; // 10%
86-
bundlerSurchargeRate = 1_000_000; // 10%
85+
atlasSurchargeRate = 1000; // 10%
86+
bundlerSurchargeRate = 1000; // 10%
8787
}
8888
}
8989

script/deploy-atlas.s.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ contract DeployAtlasScript is DeployBaseScript {
5555
atlas = new Atlas({
5656
escrowDuration: ESCROW_DURATION,
5757
atlasSurchargeRate: ATLAS_SURCHARGE_RATE,
58-
bundlerSurchargeRate: BUNDLER_SURCHARGE_RATE,
5958
verification: expectedAtlasVerificationAddr,
6059
simulator: expectedSimulatorAddr,
6160
factoryLib: address(factoryLib),

src/contracts/atlas/AtlETH.sol

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,12 @@ abstract contract AtlETH is Permit69 {
1212
constructor(
1313
uint256 escrowDuration,
1414
uint256 atlasSurchargeRate,
15-
uint256 bundlerSurchargeRate,
1615
address verification,
1716
address simulator,
1817
address initialSurchargeRecipient,
1918
address l2GasCalculator
2019
)
21-
Permit69(
22-
escrowDuration,
23-
atlasSurchargeRate,
24-
bundlerSurchargeRate,
25-
verification,
26-
simulator,
27-
initialSurchargeRecipient,
28-
l2GasCalculator
29-
)
20+
Permit69(escrowDuration, atlasSurchargeRate, verification, simulator, initialSurchargeRecipient, l2GasCalculator)
3021
{ }
3122

3223
/*//////////////////////////////////////////////////////////////
@@ -288,8 +279,4 @@ abstract contract AtlETH is Permit69 {
288279
S_pendingSurchargeRecipient = address(0);
289280
emit SurchargeRecipientTransferred(msg.sender);
290281
}
291-
292-
function _checkIfUnlocked() internal view {
293-
if (!_isUnlocked()) revert InvalidLockState();
294-
}
295282
}

src/contracts/atlas/Atlas.sol

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pragma solidity 0.8.28;
33

44
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
55
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";
68

79
import { Escrow } from "./Escrow.sol";
810
import { Factory } from "./Factory.sol";
@@ -20,10 +22,11 @@ import { GasAccLib, GasLedger } from "../libraries/GasAccLib.sol";
2022
import { IL2GasCalculator } from "../interfaces/IL2GasCalculator.sol";
2123
import { IDAppControl } from "../interfaces/IDAppControl.sol";
2224

23-
/// @title Atlas V1.5
25+
/// @title Atlas V1.6
2426
/// @author FastLane Labs
2527
/// @notice The Execution Abstraction protocol.
2628
contract Atlas is Escrow, Factory {
29+
using SafeCast for uint256;
2730
using CallBits for uint32;
2831
using SafetyBits for Context;
2932
using GasAccLib for uint256;
@@ -32,22 +35,13 @@ contract Atlas is Escrow, Factory {
3235
constructor(
3336
uint256 escrowDuration,
3437
uint256 atlasSurchargeRate,
35-
uint256 bundlerSurchargeRate,
3638
address verification,
3739
address simulator,
3840
address initialSurchargeRecipient,
3941
address l2GasCalculator,
4042
address factoryLib
4143
)
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)
5145
Factory(factoryLib)
5246
{ }
5347

@@ -123,8 +117,23 @@ contract Atlas is Escrow, Factory {
123117
// Initialize the environment lock and accounting values
124118
_setEnvironmentLock(_dConfig, _executionEnvironment);
125119

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+
});
128137

129138
// Calculate `execute` gas limit such that it can fail due to an OOG error caused by any of the hook calls, and
130139
// 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 {
152161
);
153162
}
154163

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+
);
158173

159174
auctionWon = ctx.solverSuccessful;
160175
emit MetacallResult(msg.sender, userOp.from, auctionWon, _ethPaidToBundler, _netGasSurcharge);
@@ -173,7 +188,8 @@ contract Atlas is Escrow, Factory {
173188
emit MetacallResult(msg.sender, userOp.from, false, 0, 0);
174189
}
175190

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.
177193
_releaseLock();
178194
}
179195

@@ -258,6 +274,7 @@ contract Atlas is Escrow, Factory {
258274

259275
uint256[] memory _bidsAndIndices = new uint256[](solverOpsLength);
260276
uint256 _bidAmountFound;
277+
uint256 _solverExecutionGas;
261278
uint256 _bidsAndIndicesLastIndex = solverOpsLength - 1; // Start from the last index
262279

263280
// 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 {
287304
// remainingMaxGas separately here. This decrease does not include calldata gas as solvers are not charged
288305
// for calldata gas in exPostBids mode.
289306
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();
291310
t_gasLedger = _gL.pack();
292311

293312
// skip zero and overflow bid's
@@ -378,14 +397,29 @@ contract Atlas is Escrow, Factory {
378397

379398
SolverOperation calldata solverOp = solverOps[ctx.solverIndex];
380399

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(
382403
ctx, dConfig, userOp, solverOp, solverOp.bidAmount, _gasWaterMark, false, returnData
383404
);
384405

406+
// If a winning solver is found, stop iterating through the solverOps and return the winning bid
385407
if (ctx.solverSuccessful) {
386408
return _bidAmount;
387409
}
388410
}
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+
389423
if (ctx.isSimulation) revert SolverSimFail(uint256(ctx.solverOutcome));
390424
if (dConfig.callConfig.needsFulfillment()) revert UserNotFulfilled();
391425
return 0;

src/contracts/atlas/AtlasVerification.sol

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pragma solidity 0.8.28;
44
import { EIP712 } from "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol";
55
import { ECDSA } from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
66
import { SignatureChecker } from "openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol";
7+
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
8+
79
import { DAppIntegration } from "./DAppIntegration.sol";
810
import { NonceManager } from "./NonceManager.sol";
911

@@ -33,7 +35,7 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
3335
address atlas,
3436
address l2GasCalculator
3537
)
36-
EIP712("AtlasVerification", "1.5")
38+
EIP712("AtlasVerification", "1.6")
3739
DAppIntegration(atlas, l2GasCalculator)
3840
{ }
3941

@@ -164,6 +166,23 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
164166
return
165167
(allSolversGasLimit, allSolversCalldataGas, bidFindOverhead, ValidCallsResult.DAppGasLimitMismatch);
166168
}
169+
170+
// Check the solverGasLimit read from DAppControl at start of metacall matches userOp value
171+
if (dConfig.solverGasLimit != userOp.solverGasLimit) {
172+
return (
173+
allSolversGasLimit, allSolversCalldataGas, bidFindOverhead, ValidCallsResult.SolverGasLimitMismatch
174+
);
175+
}
176+
177+
// Check the bundlerSurchargeRate read from DAppControl at start of metacall matches userOp value
178+
if (dConfig.bundlerSurchargeRate != userOp.bundlerSurchargeRate) {
179+
return (
180+
allSolversGasLimit,
181+
allSolversCalldataGas,
182+
bidFindOverhead,
183+
ValidCallsResult.BundlerSurchargeRateMismatch
184+
);
185+
}
167186
}
168187

169188
// Check gasleft() measured at start of metacall is in line with expected gas limit
@@ -264,7 +283,26 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
264283
// Max one of preOps or userOp return data can be tracked, not both
265284
return ValidCallsResult.InvalidCallConfig;
266285
}
267-
286+
if (callConfig.multipleSuccessfulSolvers() && callConfig.exPostBids()) {
287+
// Max one of multipleSolvers or exPostBids can be used, not both
288+
return ValidCallsResult.ExPostBidsAndMultipleSuccessfulSolversNotSupportedTogether;
289+
}
290+
if (callConfig.multipleSuccessfulSolvers() && callConfig.invertsBidValue()) {
291+
// Max one of multipleSolvers or invertsBidValue can be used, not both
292+
return ValidCallsResult.InvertsBidValueAndMultipleSuccessfulSolversNotSupportedTogether;
293+
}
294+
if (callConfig.multipleSuccessfulSolvers() && callConfig.allowsZeroSolvers()) {
295+
// Max one of multipleSolvers or allowsZeroSolvers can be used, not both
296+
return ValidCallsResult.NeedSolversForMultipleSuccessfulSolvers;
297+
}
298+
if (callConfig.multipleSuccessfulSolvers() && callConfig.allowsSolverAuctioneer()) {
299+
// Max one of multipleSolvers or allowsSolverAuctioneer can be used, not both
300+
return ValidCallsResult.SolverCannotBeAuctioneerForMultipleSuccessfulSolvers;
301+
}
302+
if (callConfig.multipleSuccessfulSolvers() && callConfig.needsFulfillment()) {
303+
// Max one of multipleSolvers or needsFulfillment can be used, not both
304+
return ValidCallsResult.CannotRequireFulfillmentForMultipleSuccessfulSolvers;
305+
}
268306
if (callConfig.needsSequentialUserNonces() && callConfig.needsSequentialDAppNonces()) {
269307
// Max one of user or dapp nonces can be sequential, not both
270308
return ValidCallsResult.BothUserAndDAppNoncesCannotBeSequential;
@@ -324,7 +362,6 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
324362
}
325363

326364
if (dConfig.callConfig.allowsUnknownAuctioneer()) return (ValidCallsResult.Valid, true);
327-
328365
return (ValidCallsResult.Valid, false);
329366
}
330367

@@ -571,6 +608,8 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
571608
userOp.control,
572609
userOp.callConfig,
573610
userOp.dappGasLimit,
611+
userOp.solverGasLimit,
612+
userOp.bundlerSurchargeRate,
574613
userOp.sessionKey
575614
)
576615
)
@@ -591,6 +630,8 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
591630
userOp.control,
592631
userOp.callConfig,
593632
userOp.dappGasLimit,
633+
userOp.solverGasLimit,
634+
userOp.bundlerSurchargeRate,
594635
userOp.sessionKey,
595636
keccak256(userOp.data)
596637
)
@@ -624,10 +665,20 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
624665
uint256 bidFindOverhead
625666
)
626667
{
627-
allSolversCalldataGas = solverOps.sumSolverOpsCalldataGas(L2_GAS_CALCULATOR);
628668
uint256 solverOpsLen = solverOps.length;
629669
uint256 dConfigSolverGasLimit = dConfig.solverGasLimit;
630-
uint256 allSolversExecutionGas = solverOpsLen * dConfigSolverGasLimit;
670+
uint256 solverDataLenSum; // Calculated as sum of solverOps[i].data.length below
671+
uint256 allSolversExecutionGas; // Calculated as sum of solverOps[i].gas below
672+
673+
for (uint256 i = 0; i < solverOpsLen; ++i) {
674+
// Sum calldata length of all solverOp.data fields in the array
675+
solverDataLenSum += solverOps[i].data.length;
676+
// Sum all solverOp.gas values in the array, each with a ceiling of dConfig.solverGasLimit
677+
allSolversExecutionGas += Math.min(solverOps[i].gas, dConfigSolverGasLimit);
678+
}
679+
680+
allSolversCalldataGas =
681+
GasAccLib.calldataGas(solverDataLenSum + (_SOLVER_OP_BASE_CALLDATA * solverOps.length), L2_GAS_CALCULATOR);
631682

632683
uint256 metacallExecutionGas = _BASE_TX_GAS_USED + AccountingMath._FIXED_GAS_OFFSET + userOpGas
633684
+ dConfig.dappGasLimit + allSolversExecutionGas;
@@ -637,7 +688,7 @@ contract AtlasVerification is EIP712, NonceManager, DAppIntegration {
637688

638689
if (dConfig.callConfig.exPostBids()) {
639690
// Add extra execution gas for bid-finding loop of each solverOp
640-
bidFindOverhead = solverOpsLen * (_BID_FIND_OVERHEAD + dConfigSolverGasLimit);
691+
bidFindOverhead = (solverOpsLen * _BID_FIND_OVERHEAD) + allSolversExecutionGas;
641692
metacallExecutionGas += bidFindOverhead;
642693
// NOTE: allSolversGasLimit excludes calldata in exPostBids mode.
643694
} else {

0 commit comments

Comments
 (0)