atlas
Cantina Security Report
Organization
- @Fastlane
Engagement Type
Cantina Reviews
Period
-
Repositories
Researchers
Findings
Medium Risk
2 findings
2 fixed
0 acknowledged
Low Risk
2 findings
2 fixed
0 acknowledged
Informational
4 findings
3 fixed
1 acknowledged
Gas Optimizations
1 findings
1 fixed
0 acknowledged
Medium Risk2 findings
Solvers may receive less gas than they expect
State
Severity
- Severity: Medium
Submitted by
Riley Holterhus
Description
A key change introduced in Atlas V1.6 is that the gas limit forwarded to solvers is now set as the minimum of
solverOp.gas
(the value set by the solver) anddConfig.solverGasLimit
(the value from the DAppControl).One consequence of this change is that if
dConfig.solverGasLimit
is lowered after a solver signs and submits their solverOp, the gas limit they ultimately receive will be lower than they expect. In a worst-case scenario, a DAppControl could intentionally lower thedConfig.solverGasLimit
value to cause the solverOp to fail due to an out-of-gas error. This could grief the solver since an out-of-gas error would be treated as their fault.Recommendation
Consider adding a field to the userOp struct to commit to the expected
dConfig.solverGasLimit
from the DAppControl, similar to howbundlerSurchargeRate
is included in the userOp. This approach would only add one new field and ensures that all solverOps are aware of the expecteddConfig.solverGasLimit
, since it contributes to the userOp hash.Fastlane
Fixed in commit 1ef75ad and commit 8b217b3.
Cantina Managed
Verified.
Net repayments can be counted across multiple solverOps
State
- Fixed
PR #482
Severity
- Severity: Medium
Submitted by
Riley Holterhus
Description
A solverOp's balance is considered reconciled if all current repayments are at least equal to all current borrows, and the solver has also prepaid their maximum gas liability, either through approving their bonded atlETH or with an excess repayment. This is implemented in the following code:
function _isBalanceReconciled() internal view returns (bool) { // .... return (bL.repays >= bL.borrows) && (_maxApprovedGasValue + _netRepayments >= gL.solverGasLiability());}
However, this logic does not function correctly when
multipleSuccessfulSolvers == true
, because the same net repayment can be counted toward multiple solverOps.For example, if one solverOp leaves behind a 1 ETH net repayment and there are 10 solverOps, each can independently apply the same 1 ETH toward their own gas liability check. There is no mechanism to track how much of the repayment each solverOp is relying on, which allows the same ETH to be reused across multiple solverOps. As a result,
_isBalanceReconciled()
may incorrectly return true for all solverOps, even if the excess is only sufficient to cover one.This can lead to a shortfall in the gas reimbursement that only becomes apparent during the final
_settle()
call, at which point the bundler would implicitly cover the difference.Recommendation
Change the gas accounting logic so that after each solverOp, the portion of the net repayment used toward its gas liability is subtracted out, preventing other solverOps from reusing the same amount.
Fastlane
Cantina Managed
Verified. The chosen fix is to exclude net repayments from the gas liability check when
multipleSuccessfulSolvers == true
, which also fixes the issue.
Low Risk2 findings
solverGasLiability() overestimated when multipleSuccessfulSolvers == true
State
- Fixed
PR #480
Severity
- Severity: Low
Submitted by
Riley Holterhus
Description
The
solverGasLiability()
function estimates the upper bound amount of gas a successful solver must pay. Its implementation predates themultipleSuccessfulSolvers == true
configuration option and assumes a successful solver must cover all overhead gas costs (e.g. userOp and dapp hook costs). However, whenmultipleSuccessfulSolvers
is true, solvers only need to pay for their own calldata and execution gas costs. As a result, the current implementation overestimates the gas liability and requires larger prepayments than necessary.Recommendation
Consider updating the accounting logic so that when
multipleSuccessfulSolvers == true
, successful solvers only need prepay their own execution and calldata costs.Fastlane
Fixed in PR 480 by changing the
_initialRemainingMaxGas
value set at the start of a metacall.Cantina Managed
Verified.
Unsafe typecasts
State
Severity
- Severity: Low
Submitted by
Blockdev
Description
Several variables are downcasted from
uint256
touint40
and there are no guarantees for some of them to not overflow. Hence, it's better to useSafeCast
.Recommendation
Use
SafeCast
to revert if variables overflowuint40
.Fastlane
Fixed in commit 5728314 and commit fb9eac9.
Cantina Managed
Verified.
Informational4 findings
Unused surcharge mask constants
State
Severity
- Severity: Informational
Submitted by
Riley Holterhus
Description
The
_ATLAS_SURCHARGE_MASK
and_BUNDLER_SURCHARGE_MASK
constants inAtlasConstants
are unused. These variables are no longer relevant because the bundler surcharge is now a configurable value in the DAppControl instead of being stored in Atlas, and the Atlas surcharge isn't in a packed storage variable.Recommendation
Consider removing the unused
_ATLAS_SURCHARGE_MASK
and_BUNDLER_SURCHARGE_MASK
variables.Fastlane
Fixed in commit b036575.
Cantina Managed
Verified.
Incorrect comments in _verifyCallConfig()
State
Severity
- Severity: Informational
Submitted by
Riley Holterhus
Description
The
multipleSuccessfulSolvers
configuration option is not allowed whenallowsZeroSolvers
,allowsSolverAuctioneer
, orneedsFulfillment
are true. While this logic is correctly enforced in the code, the related comments for these three options incorrectly referenceinvertsBidValue
instead.Recommendation
Update the comments to reference the correct disallowed options.
Fastlane
Fixed in commit 5613618.
Cantina Managed
Verified.
Analytics consider all solvers as failed when multipleSuccessfulSolvers == true
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
Riley Holterhus
Description
When
multipleSuccessfulSolvers
is true, the gas accounting is handled as if all solverOps reverted. This can be seen by the fact that_executeSolverOperation()
always calls_handleSolverFailAccounting()
whenmultipleSuccessfulSolvers == true
, regardless of whether the solverOp succeeded or failed.One side effect of this is that the analytics are also updated as if every solver failed, even though some of the solvers may have succeeded and some of them may have failed.
Recommendation
Consider updating the logic so that when
multipleSuccessfulSolvers
is true, the analytics correctly distinguish between successful and failed solvers.Fastlane
Acknowledged.
Cantina Managed
Acknowledged.
Missing Natspec
State
Severity
- Severity: Informational
Submitted by
Blockdev
Description
A new argument
multipleSuccessfulSolvers
is added to_settle()
but it's not added to its Natspec.Recommendation
Add Natspec for
multipleSuccessfulSolvers
.Fastlane
Fixed in commit 9dbafd.
Cantina Managed
Verified.
Gas Optimizations1 finding
Use OZ Math.min()
State
Severity
- Severity: Gas optimization
Submitted by
Blockdev
Description
OZ's
Math.min()
can be used instead of the ternary operator to compute a minimum of two values. It's easier to read and may also save some gas asMath.min()
avoids JUMPs.Recommendation
Use
Math.min()
.Fastlane
Fixed here 482c43b8d9850eb2d2d1abccb7850fb3bf715308.
FYI it's also relevant to Escrow.sol, AtlasVerification.sol, Simulator.sol, and Sorter.sol.
Cantina Managed
Verified.