Skip to content

Conversation

BenSparksCode
Copy link
Contributor

@BenSparksCode BenSparksCode commented Apr 18, 2025

Changes vs Atlas v1.5:

A new multipleSuccessfulSolvers call config setting.

In this config multiple solvers can execute successfully and each pay a bid. Atlas will continue to revert solvers who do not pay their bid in this config. This config enables app specific ordering rules, rather than a standard backrun auction. If the MEV is taken by an earlier solver in this config, the next solver should either revert early or search for leftover MEV. The gas accounting should be the same as if all solverOps reverted, even if solvers executed successfully and paid their bid. This config does not intend to fully compensate the bundler, the bundler will function like an oracle service with gas costs as an expense, and a monetization outside of Atlas. That being said, the bundler expects each solver to cover their respective gas costs in this config the same as if all SolverOps reverted.

It works by:

  • Overriding successful solver _results to the new SolverOutcome.MultipleSolvers.
  • Ensuring ctx.solverSuccessful is never set to true so the next solver always executes.
  • Assigning all solvers to gas accounting as if they failed with a FULL_REFUND.
  • If there are multiple solvers with bidAmount > 0 that execute successfully, the bid amounts are summed. The sum is passed to allocateValue.
  • settle() and allocateValue() hooks are called with ctx.solverSuccessful set to false, even though all solvers may have executed successfully and paid their bid. The DappControl will understand and handle this in the allocateValue implementation.
  • Both the bundler and auctioneer roles in this config will be performed by the same entity, the "oracle service", so the bundler and the auctioneer roles in this config can be trusted not to attack solvers, and not to attack each other. The audit should focus on ensuring that this config does not allow solvers to attack the bundler, auctioneer, or app; reputation will be used alongside the Atlas smart contracts to help prevent this.

This config is not designed to be used with the invertBidValue, solverAuctioneer, requireFulfillment, zeroSolvers, or the exPostBids config, this is enforced in AtlasVerification.

Bundler surcharge set at the DAppControl level, instead of a global setting at the Atlas level.

  • Bundler surcharge rate (as a percentage of 10_000_000) is defined by the DAppControl in the getBundlerSurchargeRate() view function which returns a uint128
  • UserOperation now includes a uint128 bundlerSurchargeRate field which is included in the typehash in both normal and trustedOpHash modes. As such, the user and solvers all implicitly agree to a specific bundlerSurchargeRate value before signing their operations.
  • The USER_OP_STATIC_LENGTH constant (which measures the length of a UserOp with empty sig and data fields, for calldata gas accounting) has been increased by 32, bringing it to a value of 576.
  • userOp.bundlerSurchargeRate is included in the hash operation in AtlasVerification.getUserOperationHash(), in both default and trusted modes.
  • AtlasVerification checks that the bundler surcharge rate value in the UserOperation matches the value in the DAppControl's dConfig, at the start of the metacall. If those values do not match, the metacall will revert due to BundlerSurchargeRateMismatch.
  • The bundler surcharge rate is then stored in the right-most 128 bits in the S_surchargeRates storage slot, with the left-most 128 bits taken up by the Atlas surcharge rate. This is the same as before, and the Atlas logic which reads the surcharges rates for calculations has not been modified.
  • The bundler surcharge rate in Atlas' S_surchargeRates storage variable is reset to 0 at the end of the metacall.
  • the bundlerSurchargeRate() view function has been removed from Atlas and the IAtlas interface, as it is only non-zero during a metacall, and at that point it can be read of the active DAppControl.
  • The Simulator and Sorter now use the userOp.bundlerSurchargeRate figure, as Atlas.bundlerSurchargeRate() has been removed.
  • When set in storage, both the Atlas and Bundler surcharge rates are checked to be less than type(uint128).max.

Use solverOp.gas instead of dConfig.solverGasLimit for lower and more accurate gas estimations.

This PR aims to lower the recommended gas limit bundlers need to use for metacalls, and lower the bonded atlETH required to qualify a solverOp for execution. It also brings standard Atlas closer to Atlas on Monad, as the Monad version already uses this approach.

Changes:

  • Uses a for-loop to calculate the sum of solverOp.gas values in the solverOps array, instead of the previous solverOps.length * dConfig.solverGasLimit approach.
  • When decreasing remainingMaxGas and unreachedSolverGas for gas accounting purposes, solverOp.gas is used instead of dConfig.solverGasLimit.
  • In both points above, there is a ceiling of dConfig.solverGasLimit applied to the solverOp.gas figure added or subtracted, so the value cannot be problematically large due to solver tampering.

Read Atlas and Bundler surcharge rates from GasLedger struct for better gas efficiency.

  • Changed the 5 previous fields of GasLedger struct from uint48 to uint40, to free up space in the single-slot GasLedger variable for the 2 surcharge rates. Each of these vars can now only store a max of 1.09 x 10^12 gas. This should be enough gas even on chains with gigagas (10^9) block limits.
  • Added uint24 atlasSurchargeRate and uint24 bundlerSurchargeRate to the GasLedger struct.
  • Both surcharge rates are set in the struct along with the previous 5 values, at the start of the metacall, in _initializeAccountingValues(). Atlas surcharge rate is read from the S_atlasSurchargeRate slot in Atlas, while the bundler surcharge rate is taken from the userOp.bundlerSurchargeRate value, which is checked against the dConfig.bundlerSurchargeRate value during validateCalls() to ensure the userOp and DAppControl agree.
  • This allows us to remove the extra SLOAD (only 100 gas per SLOAD as slot was always warm) whenever we needed the total surcharge rate to perform the solverGasLiability() calculation.
  • The denominator used in these rates (the SCALE constant) has been reduced from 10_000_000 to 10_000. Because the max surcharge rate value that can be stored in a uint24 is 16 777 215, this means the surcharge rates have a max of 167.77x or 16 777% each.
  • The atlasSurchargeRate() view function on Atlas has been changed to getAtlasSurchargeRate() to avoid name collisions.

Audit Fixes

Jacob Greene and others added 30 commits March 19, 2025 10:55
metacallSimulation accounts for multipleSuccessfulSolvers in Simulato…
Base automatically changed from atlas-v1.5 to main April 21, 2025 05:17
@BenSparksCode BenSparksCode changed the title Atlas v1.6 audit feat: Atlas v1.6 May 8, 2025
@BenSparksCode BenSparksCode merged commit 615542b into main May 13, 2025
3 checks passed
@BenSparksCode BenSparksCode deleted the atlas-v1.6-audit branch May 13, 2025 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants