diff --git a/LICENSE b/LICENSE index 3f19c92f..11b0fd73 100644 --- a/LICENSE +++ b/LICENSE @@ -9,14 +9,10 @@ Parameters Licensor: Fastlane Labs -Licensed Work: FastLane Protocol - The Licensed Work is (c) 2023 Fastlane Labs +Licensed Work: Atlas Protocol + The Licensed Work is (c) 2024 Fastlane Labs -Additional Use Grant: Any uses listed and defined at - fastlane-protocol-license.fastlane.finance - -Change Date: The earlier of 2025-07-01 or a date specified at - fastlane-protocol-license.fastlane.finance +Change Date: 2026-07-01 Change License: GNU General Public License v2.0 or later diff --git a/lib/forge-std b/lib/forge-std index e4aef94c..5a802d7c 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit e4aef94c1768803a16fe19f7ce8b65defd027cfd +Subproject commit 5a802d7c10abb4bbfb3e7214c75052ef9e6a06f8 diff --git a/lib/solady b/lib/solady index bb4b43b4..42af395e 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit bb4b43b44bec3c5d42604c08904bca0442e0bc78 +Subproject commit 42af395e631fcc9d640eddf11c57c6f1ca3f9103 diff --git a/package.json b/package.json index 87452d74..fa096bdc 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "solver-deposit": "source .env && forge script script/solver-deposit.s.sol:SolverAtlasDepositScript --fork-url http://localhost:8545 --broadcast --non-interactive", "setup-demo": "npm run deploy-atlas-swap-intent-tx-builder && npm run deploy-solver && npm run solver-deposit", + "deploy-gas-calculator-base": "source .env && forge script script/deploy-gas-calculator.s.sol:DeployGasCalculatorScript --rpc-url ${BASE_RPC_URL} --broadcast --etherscan-api-key ${ETHERSCAN_API_KEY} --verify", + "atlas-addr": "echo 'ATLAS:' && jq -r '.ATLAS' deployments.json", "swap-intent-addr": "echo 'SWAP INTENT DAPP CONTROL:' && jq -r '.SWAP_INTENT_DAPP_CONTROL' deployments.json", "tx-builder-addr": "echo 'TX BUILDER:' && jq -r '.TX_BUILDER' deployments.json", diff --git a/script/deploy-atlas.s.sol b/script/deploy-atlas.s.sol index 8531c62e..2cc7eb95 100644 --- a/script/deploy-atlas.s.sol +++ b/script/deploy-atlas.s.sol @@ -14,6 +14,10 @@ import { Sorter } from "src/contracts/helpers/Sorter.sol"; import { ExecutionEnvironment } from "src/contracts/common/ExecutionEnvironment.sol"; contract DeployAtlasScript is DeployBaseScript { + uint256 ESCROW_DURATION = 64; + uint256 ATLAS_SURCHARGE_RATE = 1_000_000; // 10% + uint256 BUNDLER_SURCHARGE_RATE = 1_000_000; // 10% + function run() external { console.log("\n=== DEPLOYING Atlas ===\n"); @@ -37,7 +41,9 @@ contract DeployAtlasScript is DeployBaseScript { ExecutionEnvironment execEnvTemplate = new ExecutionEnvironment(expectedAtlasAddr); atlas = new Atlas({ - escrowDuration: 64, + escrowDuration: ESCROW_DURATION, + atlasSurchargeRate: ATLAS_SURCHARGE_RATE, + bundlerSurchargeRate: BUNDLER_SURCHARGE_RATE, verification: expectedAtlasVerificationAddr, simulator: expectedSimulatorAddr, executionTemplate: address(execEnvTemplate), diff --git a/script/deploy-gas-calculator.s.sol b/script/deploy-gas-calculator.s.sol new file mode 100644 index 00000000..3866bbaf --- /dev/null +++ b/script/deploy-gas-calculator.s.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.25; + +import "forge-std/Test.sol"; + +import { DeployBaseScript } from "script/base/deploy-base.s.sol"; +import { BaseGasCalculator } from "src/contracts/gasCalculator/BaseGasCalculator.sol"; + +contract DeployGasCalculatorScript is DeployBaseScript { + // NOTE: Adjust the constructor parameters as needed here: + // - BASE_GAS_PRICE_ORACLE: The address of the gas price oracle contract + // - BASE_CALLDATA_LENGTH_OFFSET: The offset to be applied to the calldata length (can be negative or positive) + // ----------------------------------------------------------------------------------------------- + address constant BASE_GAS_PRICE_ORACLE = address(0x420000000000000000000000000000000000000F); + int256 constant BASE_CALLDATA_LENGTH_OFFSET = 0; // can be negative or positive + // ----------------------------------------------------------------------------------------------- + + function run() external { + console.log("\n=== DEPLOYING GasCalculator ===\n"); + + console.log("Deploying to chain: \t\t", _getDeployChain()); + + uint256 deployerPrivateKey = vm.envUint("GOV_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + console.log("Deployer address: \t\t", deployer); + + uint256 chainId = block.chainid; + address deploymentAddr; + + vm.startBroadcast(deployerPrivateKey); + + if (chainId == 8453 || chainId == 84_532) { + // Base or Base Sepolia + BaseGasCalculator gasCalculator = new BaseGasCalculator({ + gasPriceOracle: BASE_GAS_PRICE_ORACLE, + calldataLenOffset: BASE_CALLDATA_LENGTH_OFFSET + }); + deploymentAddr = address(gasCalculator); + } else { + revert("Error: Chain ID not supported"); + } + + vm.stopBroadcast(); + + _writeAddressToDeploymentsJson("L2_GAS_CALCULATOR", deploymentAddr); + + console.log("\n"); + console.log("-------------------------------------------------------------------------------"); + console.log("| Contract | Address |"); + console.log("-------------------------------------------------------------------------------"); + console.log("| L2_GAS_CALCULATOR (Base) | ", address(deploymentAddr), " |"); + console.log("-------------------------------------------------------------------------------"); + console.log("\n"); + console.log("You can find a list of contract addresses from the latest deployment in deployments.json"); + } +} diff --git a/src/contracts/atlas/AtlETH.sol b/src/contracts/atlas/AtlETH.sol index e39120fb..d9bf8a65 100644 --- a/src/contracts/atlas/AtlETH.sol +++ b/src/contracts/atlas/AtlETH.sol @@ -11,12 +11,22 @@ import "src/contracts/types/EscrowTypes.sol"; abstract contract AtlETH is Permit69 { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator ) - Permit69(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator) + Permit69( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator + ) { } /*////////////////////////////////////////////////////////////// diff --git a/src/contracts/atlas/Atlas.sol b/src/contracts/atlas/Atlas.sol index e709fa2f..909e4c56 100644 --- a/src/contracts/atlas/Atlas.sol +++ b/src/contracts/atlas/Atlas.sol @@ -4,8 +4,6 @@ pragma solidity 0.8.25; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { LibSort } from "solady/utils/LibSort.sol"; -import { IDAppControl } from "../interfaces/IDAppControl.sol"; - import { Escrow } from "./Escrow.sol"; import { Factory } from "./Factory.sol"; @@ -19,6 +17,7 @@ import "src/contracts/types/ValidCalls.sol"; import { CallBits } from "src/contracts/libraries/CallBits.sol"; import { SafetyBits } from "src/contracts/libraries/SafetyBits.sol"; import { IL2GasCalculator } from "src/contracts/interfaces/IL2GasCalculator.sol"; +import { IDAppControl } from "src/contracts/interfaces/IDAppControl.sol"; /// @title Atlas V1 /// @author FastLane Labs @@ -29,13 +28,23 @@ contract Atlas is Escrow, Factory { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator, address executionTemplate ) - Escrow(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator) + Escrow( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator + ) Factory(executionTemplate) { } diff --git a/src/contracts/atlas/Escrow.sol b/src/contracts/atlas/Escrow.sol index e133cd20..f6a3e29e 100644 --- a/src/contracts/atlas/Escrow.sol +++ b/src/contracts/atlas/Escrow.sol @@ -6,7 +6,7 @@ import { IExecutionEnvironment } from "src/contracts/interfaces/IExecutionEnviro import { IAtlas } from "src/contracts/interfaces/IAtlas.sol"; import { ISolverContract } from "src/contracts/interfaces/ISolverContract.sol"; import { IAtlasVerification } from "src/contracts/interfaces/IAtlasVerification.sol"; -import { IDAppControl } from "../interfaces/IDAppControl.sol"; +import { IDAppControl } from "src/contracts/interfaces/IDAppControl.sol"; import { SafeCall } from "src/contracts/libraries/SafeCall/SafeCall.sol"; import { EscrowBits } from "src/contracts/libraries/EscrowBits.sol"; @@ -31,12 +31,22 @@ abstract contract Escrow is AtlETH { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator ) - AtlETH(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator) + AtlETH( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator + ) { if (escrowDuration == 0) revert InvalidEscrowDuration(); } @@ -610,8 +620,8 @@ abstract contract Escrow is AtlETH { solverOp.bidToken, bidAmount, solverOp.data, - // Only pass the returnData to solver if it came from userOp call and not from preOps call. - _activeCallConfig().needsUserReturnData() ? returnData : new bytes(0) + // Only pass the returnData (either from userOp or preOps) if the dApp requires it + _activeCallConfig().forwardReturnData() ? returnData : new bytes(0) ) ) ); diff --git a/src/contracts/atlas/GasAccounting.sol b/src/contracts/atlas/GasAccounting.sol index 63f9cdd4..ab1053b0 100644 --- a/src/contracts/atlas/GasAccounting.sol +++ b/src/contracts/atlas/GasAccounting.sol @@ -21,12 +21,22 @@ abstract contract GasAccounting is SafetyLocks { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator ) - SafetyLocks(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator) + SafetyLocks( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator + ) { } /// @notice Sets the initial accounting values for the metacall transaction. @@ -35,10 +45,10 @@ abstract contract GasAccounting is SafetyLocks { uint256 _rawClaims = (FIXED_GAS_OFFSET + gasMarker) * tx.gasprice; // Set any withdraws or deposits - _setClaims(_rawClaims.withBundlerSurcharge()); + _setClaims(_rawClaims.withSurcharge(BUNDLER_SURCHARGE_RATE)); // Atlas surcharge is based on the raw claims value. - _setFees(_rawClaims.getAtlasSurcharge()); + _setFees(_rawClaims.getSurcharge(ATLAS_SURCHARGE_RATE)); _setDeposits(msg.value); // Explicitly set writeoffs and withdrawals to 0 in case multiple metacalls in single tx. @@ -281,16 +291,16 @@ abstract contract GasAccounting is SafetyLocks { if (result.bundlersFault()) { // CASE: Solver is not responsible for the failure of their operation, so we blame the bundler // and reduce the total amount refunded to the bundler - _setWriteoffs(writeoffs() + _gasUsed.withAtlasAndBundlerSurcharges()); + _setWriteoffs(writeoffs() + _gasUsed.withSurcharges(ATLAS_SURCHARGE_RATE, BUNDLER_SURCHARGE_RATE)); } else { // CASE: Solver failed, so we calculate what they owe. - uint256 _gasUsedWithSurcharges = _gasUsed.withAtlasAndBundlerSurcharges(); + uint256 _gasUsedWithSurcharges = _gasUsed.withSurcharges(ATLAS_SURCHARGE_RATE, BUNDLER_SURCHARGE_RATE); _assign(solverOp.from, _gasUsedWithSurcharges, _gasUsedWithSurcharges, false); } } function _writeOffBidFindGasCost(uint256 gasUsed) internal { - _setWriteoffs(writeoffs() + gasUsed.withAtlasAndBundlerSurcharges()); + _setWriteoffs(writeoffs() + gasUsed.withSurcharges(ATLAS_SURCHARGE_RATE, BUNDLER_SURCHARGE_RATE)); } /// @param ctx Context struct containing relevant context information for the Atlas auction. @@ -332,9 +342,9 @@ abstract contract GasAccounting is SafetyLocks { uint256 _gasRemainder = _gasLeft * tx.gasprice; // Calculate the preadjusted netAtlasGasSurcharge - netAtlasGasSurcharge = _fees - _gasRemainder.getAtlasSurcharge(); + netAtlasGasSurcharge = _fees - _gasRemainder.getSurcharge(ATLAS_SURCHARGE_RATE); - adjustedClaims -= _gasRemainder.withBundlerSurcharge(); + adjustedClaims -= _gasRemainder.withSurcharge(BUNDLER_SURCHARGE_RATE); adjustedWithdrawals += netAtlasGasSurcharge; S_cumulativeSurcharge = _surcharge + netAtlasGasSurcharge; // Update the cumulative surcharge @@ -342,7 +352,7 @@ abstract contract GasAccounting is SafetyLocks { // gas rebate. By reducing the claims, solvers end up paying less in total. if (ctx.solverCount > 0) { // Calculate the unadjusted bundler gas surcharge - uint256 _grossBundlerGasSurcharge = adjustedClaims.withoutBundlerSurcharge(); + uint256 _grossBundlerGasSurcharge = adjustedClaims.withoutSurcharge(BUNDLER_SURCHARGE_RATE); // Calculate an estimate for how much gas should be remaining // NOTE: There is a free buffer of one SolverOperation because solverIndex starts at 0. diff --git a/src/contracts/atlas/Permit69.sol b/src/contracts/atlas/Permit69.sol index 6d6f7d0b..44f5bc45 100644 --- a/src/contracts/atlas/Permit69.sol +++ b/src/contracts/atlas/Permit69.sol @@ -26,12 +26,22 @@ import "src/contracts/types/EscrowTypes.sol"; abstract contract Permit69 is GasAccounting { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator ) - GasAccounting(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator) + GasAccounting( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator + ) { } /// @notice Verifies that the caller is an authorized Execution Environment contract. diff --git a/src/contracts/atlas/SafetyLocks.sol b/src/contracts/atlas/SafetyLocks.sol index 2b3d66b9..ad9384f3 100644 --- a/src/contracts/atlas/SafetyLocks.sol +++ b/src/contracts/atlas/SafetyLocks.sol @@ -18,12 +18,22 @@ abstract contract SafetyLocks is Storage { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator ) - Storage(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator) + Storage( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator + ) { } /// @notice Sets the Atlas lock to the specified execution environment. diff --git a/src/contracts/atlas/Storage.sol b/src/contracts/atlas/Storage.sol index 4a20052b..251e4892 100644 --- a/src/contracts/atlas/Storage.sol +++ b/src/contracts/atlas/Storage.sol @@ -18,6 +18,8 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants { address public immutable SIMULATOR; address public immutable L2_GAS_CALCULATOR; uint256 public immutable ESCROW_DURATION; + uint256 public immutable ATLAS_SURCHARGE_RATE; + uint256 public immutable BUNDLER_SURCHARGE_RATE; // AtlETH public constants // These constants double as interface functions for the ERC20 standard, hence the lowercase naming convention. @@ -26,8 +28,6 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants { uint8 public constant decimals = 18; // Gas Accounting public constants - uint256 public constant ATLAS_SURCHARGE_RATE = AccountingMath._ATLAS_SURCHARGE_RATE; - uint256 public constant BUNDLER_SURCHARGE_RATE = AccountingMath._BUNDLER_SURCHARGE_RATE; uint256 public constant SCALE = AccountingMath._SCALE; uint256 public constant FIXED_GAS_OFFSET = AccountingMath._FIXED_GAS_OFFSET; @@ -56,6 +56,8 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, @@ -67,6 +69,8 @@ contract Storage is AtlasEvents, AtlasErrors, AtlasConstants { SIMULATOR = simulator; L2_GAS_CALCULATOR = l2GasCalculator; ESCROW_DURATION = escrowDuration; + ATLAS_SURCHARGE_RATE = atlasSurchargeRate; + BUNDLER_SURCHARGE_RATE = bundlerSurchargeRate; // Gas Accounting // Initialized with msg.value to seed flash loan liquidity diff --git a/src/contracts/dapp/ControlTemplate.sol b/src/contracts/dapp/ControlTemplate.sol index 2d76b212..df402abc 100644 --- a/src/contracts/dapp/ControlTemplate.sol +++ b/src/contracts/dapp/ControlTemplate.sol @@ -1,9 +1,9 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import "../types/SolverOperation.sol"; -import "../types/UserOperation.sol"; -import "../types/ConfigTypes.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; import { AtlasErrors } from "src/contracts/types/AtlasErrors.sol"; abstract contract DAppControlTemplate { diff --git a/src/contracts/gasCalculator/BaseGasCalculator.sol b/src/contracts/gasCalculator/BaseGasCalculator.sol new file mode 100644 index 00000000..97e404a6 --- /dev/null +++ b/src/contracts/gasCalculator/BaseGasCalculator.sol @@ -0,0 +1,66 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.25; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IL2GasCalculator } from "src/contracts/interfaces/IL2GasCalculator.sol"; + +/// @notice Implementation: +/// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/GasPriceOracle.sol +/// @notice Deployment on Base: https://basescan.org/address/0x420000000000000000000000000000000000000f +interface IGasPriceOracle { + function getL1FeeUpperBound(uint256 _unsignedTxSize) external view returns (uint256); +} + +contract BaseGasCalculator is IL2GasCalculator, Ownable { + uint256 internal constant _CALLDATA_LENGTH_PREMIUM = 16; + uint256 internal constant _BASE_TRANSACTION_GAS_USED = 21_000; + + address public immutable GAS_PRICE_ORACLE; + int256 public calldataLengthOffset; + + constructor(address gasPriceOracle, int256 calldataLenOffset) Ownable(msg.sender) { + GAS_PRICE_ORACLE = gasPriceOracle; + calldataLengthOffset = calldataLenOffset; + } + + /// @notice Calculate the cost of calldata in ETH on a L2 with a different fee structure than mainnet + /// @param calldataLength The length of the calldata in bytes + /// @return calldataCost The cost of the calldata in ETH + function getCalldataCost(uint256 calldataLength) external view override returns (uint256 calldataCost) { + // `getL1FeeUpperBound` returns the upper bound of the L1 fee in wei. It expects an unsigned transaction size in + // bytes, *not calldata length only*, which makes this function a rough estimate. + + // Base execution cost. + calldataCost = calldataLength * _CALLDATA_LENGTH_PREMIUM * tx.gasprice; + + // L1 data cost. + // `getL1FeeUpperBound` adds 68 to the size because it expects an unsigned transaction size. + // Remove 68 to the length to account for this. + if (calldataLength < 68) { + calldataLength = 0; + } else { + calldataLength -= 68; + } + + int256 _calldataLenOffset = calldataLengthOffset; + + if (_calldataLenOffset < 0 && calldataLength < uint256(-_calldataLenOffset)) { + return calldataCost; + } + + calldataLength += uint256(_calldataLenOffset); + calldataCost += IGasPriceOracle(GAS_PRICE_ORACLE).getL1FeeUpperBound(calldataLength); + } + + /// @notice Gets the cost of initial gas used for a transaction with a different calldata fee than mainnet + /// @param calldataLength The length of the calldata in bytes + function initialGasUsed(uint256 calldataLength) external pure override returns (uint256 gasUsed) { + return _BASE_TRANSACTION_GAS_USED + (calldataLength * _CALLDATA_LENGTH_PREMIUM); + } + + /// @notice Sets the calldata length offset + /// @param calldataLenOffset The new calldata length offset + function setCalldataLengthOffset(int256 calldataLenOffset) external onlyOwner { + calldataLengthOffset = calldataLenOffset; + } +} diff --git a/src/contracts/helpers/Simulator.sol b/src/contracts/helpers/Simulator.sol index af3620bd..34d9f3f3 100644 --- a/src/contracts/helpers/Simulator.sol +++ b/src/contracts/helpers/Simulator.sol @@ -1,19 +1,19 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import { IAtlas } from "../interfaces/IAtlas.sol"; +import { IAtlas } from "src/contracts/interfaces/IAtlas.sol"; import { AtlasErrors } from "src/contracts/types/AtlasErrors.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import "../types/SolverOperation.sol"; -import "../types/UserOperation.sol"; -import "../types/LockTypes.sol"; -import "../types/DAppOperation.sol"; -import "../types/ConfigTypes.sol"; -import "../types/ValidCalls.sol"; -import "../types/EscrowTypes.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/LockTypes.sol"; +import "src/contracts/types/DAppOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; +import "src/contracts/types/ValidCalls.sol"; +import "src/contracts/types/EscrowTypes.sol"; import { Result } from "src/contracts/interfaces/ISimulator.sol"; diff --git a/src/contracts/helpers/Sorter.sol b/src/contracts/helpers/Sorter.sol index 60225cc2..face331c 100644 --- a/src/contracts/helpers/Sorter.sol +++ b/src/contracts/helpers/Sorter.sol @@ -1,17 +1,18 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import { IAtlas } from "../interfaces/IAtlas.sol"; -import { IDAppControl } from "../interfaces/IDAppControl.sol"; +import { IAtlas } from "src/contracts/interfaces/IAtlas.sol"; +import { IDAppControl } from "src/contracts/interfaces/IDAppControl.sol"; +import { IAtlasVerification } from "src/contracts/interfaces/IAtlasVerification.sol"; + import { CallBits } from "src/contracts/libraries/CallBits.sol"; import { AccountingMath } from "src/contracts/libraries/AccountingMath.sol"; -import { CallVerification } from "../libraries/CallVerification.sol"; -import { IAtlasVerification } from "../interfaces/IAtlasVerification.sol"; -import { AtlasConstants } from "../types/AtlasConstants.sol"; +import { CallVerification } from "src/contracts/libraries/CallVerification.sol"; +import { AtlasConstants } from "src/contracts/types/AtlasConstants.sol"; -import "../types/SolverOperation.sol"; -import "../types/UserOperation.sol"; -import "../types/ConfigTypes.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; contract Sorter is AtlasConstants { using CallBits for uint32; diff --git a/src/contracts/helpers/TxBuilder.sol b/src/contracts/helpers/TxBuilder.sol index dea57c90..3e70fdc2 100644 --- a/src/contracts/helpers/TxBuilder.sol +++ b/src/contracts/helpers/TxBuilder.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IDAppControl } from "../interfaces/IDAppControl.sol"; -import { IAtlas } from "../interfaces/IAtlas.sol"; -import { IAtlasVerification } from "../interfaces/IAtlasVerification.sol"; +import { IDAppControl } from "src/contracts/interfaces/IDAppControl.sol"; +import { IAtlas } from "src/contracts/interfaces/IAtlas.sol"; +import { IAtlasVerification } from "src/contracts/interfaces/IAtlasVerification.sol"; -import "../types/SolverOperation.sol"; -import "../types/UserOperation.sol"; -import "../types/ConfigTypes.sol"; -import "../types/DAppOperation.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; +import "src/contracts/types/DAppOperation.sol"; -import { CallVerification } from "../libraries/CallVerification.sol"; -import { CallBits } from "../libraries/CallBits.sol"; +import { CallVerification } from "src/contracts/libraries/CallVerification.sol"; +import { CallBits } from "src/contracts/libraries/CallBits.sol"; import "forge-std/Test.sol"; diff --git a/src/contracts/interfaces/IAtlasVerification.sol b/src/contracts/interfaces/IAtlasVerification.sol index ed4f050f..cdd1eef7 100644 --- a/src/contracts/interfaces/IAtlasVerification.sol +++ b/src/contracts/interfaces/IAtlasVerification.sol @@ -1,12 +1,12 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import "../types/UserOperation.sol"; -import "../types/ConfigTypes.sol"; -import "../types/DAppOperation.sol"; -import "../types/SolverOperation.sol"; -import "../types/EscrowTypes.sol"; -import "../types/ValidCalls.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; +import "src/contracts/types/DAppOperation.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/EscrowTypes.sol"; +import "src/contracts/types/ValidCalls.sol"; interface IAtlasVerification { // AtlasVerification.sol diff --git a/src/contracts/interfaces/IDAppControl.sol b/src/contracts/interfaces/IDAppControl.sol index e3be1417..25f699e8 100644 --- a/src/contracts/interfaces/IDAppControl.sol +++ b/src/contracts/interfaces/IDAppControl.sol @@ -1,9 +1,9 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import "../types/UserOperation.sol"; -import "../types/SolverOperation.sol"; -import "../types/ConfigTypes.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; interface IDAppControl { function preOpsCall(UserOperation calldata userOp) external payable returns (bytes memory); diff --git a/src/contracts/interfaces/IExecutionEnvironment.sol b/src/contracts/interfaces/IExecutionEnvironment.sol index 9dcdee96..17d7ecb5 100644 --- a/src/contracts/interfaces/IExecutionEnvironment.sol +++ b/src/contracts/interfaces/IExecutionEnvironment.sol @@ -1,10 +1,10 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import "../types/SolverOperation.sol"; -import "../types/UserOperation.sol"; -import "../types/ConfigTypes.sol"; -import "../types/EscrowTypes.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; +import "src/contracts/types/EscrowTypes.sol"; interface IExecutionEnvironment { function preOpsWrapper(UserOperation calldata userOp) external returns (bytes memory preOpsData); diff --git a/src/contracts/interfaces/IL2GasCalculator.sol b/src/contracts/interfaces/IL2GasCalculator.sol index 33f7cc0b..387aab8e 100644 --- a/src/contracts/interfaces/IL2GasCalculator.sol +++ b/src/contracts/interfaces/IL2GasCalculator.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.25; interface IL2GasCalculator { /// @notice Calculate the cost of calldata in ETH on a L2 with a different fee structure than mainnet - function getCalldataCost(uint256 length) external view returns (uint256 calldataCostETH); + function getCalldataCost(uint256 calldataLength) external view returns (uint256 calldataCost); /// @notice Gets the cost of initial gas used for a transaction with a different calldata fee than mainnet function initialGasUsed(uint256 calldataLength) external view returns (uint256 gasUsed); } diff --git a/src/contracts/interfaces/ISimulator.sol b/src/contracts/interfaces/ISimulator.sol index 97f5852f..8a92a2ce 100644 --- a/src/contracts/interfaces/ISimulator.sol +++ b/src/contracts/interfaces/ISimulator.sol @@ -1,9 +1,9 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import "../types/SolverOperation.sol"; -import "../types/UserOperation.sol"; -import "../types/ConfigTypes.sol"; +import "src/contracts/types/SolverOperation.sol"; +import "src/contracts/types/UserOperation.sol"; +import "src/contracts/types/ConfigTypes.sol"; import "src/contracts/types/DAppOperation.sol"; enum Result { diff --git a/src/contracts/libraries/AccountingMath.sol b/src/contracts/libraries/AccountingMath.sol index d30c044e..2fd43783 100644 --- a/src/contracts/libraries/AccountingMath.sol +++ b/src/contracts/libraries/AccountingMath.sol @@ -3,27 +3,40 @@ pragma solidity 0.8.25; library AccountingMath { // Gas Accounting public constants - uint256 internal constant _ATLAS_SURCHARGE_RATE = 1_000_000; // out of 10_000_000 = 10% - uint256 internal constant _BUNDLER_SURCHARGE_RATE = 1_000_000; // out of 10_000_000 = 10% uint256 internal constant _SOLVER_GAS_LIMIT_BUFFER_PERCENTAGE = 500_000; // out of 10_000_000 = 5% uint256 internal constant _SCALE = 10_000_000; // 10_000_000 / 10_000_000 = 100% uint256 internal constant _FIXED_GAS_OFFSET = 85_000; - function withBundlerSurcharge(uint256 amount) internal pure returns (uint256 adjustedAmount) { - adjustedAmount = amount * (_SCALE + _BUNDLER_SURCHARGE_RATE) / _SCALE; + function withSurcharge(uint256 amount, uint256 surchargeRate) internal pure returns (uint256 adjustedAmount) { + adjustedAmount = amount * (_SCALE + surchargeRate) / _SCALE; } - function withoutBundlerSurcharge(uint256 amount) internal pure returns (uint256 unadjustedAmount) { - unadjustedAmount = amount * _SCALE / (_SCALE + _BUNDLER_SURCHARGE_RATE); + function withoutSurcharge(uint256 amount, uint256 surchargeRate) internal pure returns (uint256 unadjustedAmount) { + unadjustedAmount = amount * _SCALE / (_SCALE + surchargeRate); } - function withAtlasAndBundlerSurcharges(uint256 amount) internal pure returns (uint256 adjustedAmount) { - adjustedAmount = amount * (_SCALE + _ATLAS_SURCHARGE_RATE + _BUNDLER_SURCHARGE_RATE) / _SCALE; + function withSurcharges( + uint256 amount, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate + ) + internal + pure + returns (uint256 adjustedAmount) + { + adjustedAmount = amount * (_SCALE + atlasSurchargeRate + bundlerSurchargeRate) / _SCALE; } // gets the Atlas surcharge from an unadjusted amount - function getAtlasSurcharge(uint256 amount) internal pure returns (uint256 surcharge) { - surcharge = amount * _ATLAS_SURCHARGE_RATE / _SCALE; + function getSurcharge( + uint256 unadjustedAmount, + uint256 surchargeRate + ) + internal + pure + returns (uint256 surchargeAmount) + { + surchargeAmount = unadjustedAmount * surchargeRate / _SCALE; } function solverGasLimitScaledDown( diff --git a/src/contracts/libraries/CallBits.sol b/src/contracts/libraries/CallBits.sol index f5c1d0a6..62b46a46 100644 --- a/src/contracts/libraries/CallBits.sol +++ b/src/contracts/libraries/CallBits.sol @@ -1,9 +1,9 @@ //SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.25; -import { IDAppControl } from "../interfaces/IDAppControl.sol"; +import { IDAppControl } from "src/contracts/interfaces/IDAppControl.sol"; -import "../types/ConfigTypes.sol"; +import "src/contracts/types/ConfigTypes.sol"; library CallBits { uint32 internal constant _ONE = uint32(1); diff --git a/src/contracts/solver/SolverBase.sol b/src/contracts/solver/SolverBase.sol index 88cccac6..0ef23806 100644 --- a/src/contracts/solver/SolverBase.sol +++ b/src/contracts/solver/SolverBase.sol @@ -42,7 +42,7 @@ contract SolverBase is ISolverContract { address bidToken, uint256 bidAmount, bytes calldata solverOpData, - bytes calldata + bytes calldata forwardedData ) external payable diff --git a/src/contracts/solver/src/TestSolver.sol b/src/contracts/solver/src/TestSolver.sol index 9eb86bce..0e9a2247 100644 --- a/src/contracts/solver/src/TestSolver.sol +++ b/src/contracts/solver/src/TestSolver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { SolverBase } from "../SolverBase.sol"; +import { SolverBase } from "src/contracts/solver/SolverBase.sol"; // Flashbots opensource repo import { BlindBackrun } from "./BlindBackrun/BlindBackrun.sol"; diff --git a/src/contracts/solver/src/TestSolverExPost.sol b/src/contracts/solver/src/TestSolverExPost.sol index 9ae76220..2bf3cd0e 100644 --- a/src/contracts/solver/src/TestSolverExPost.sol +++ b/src/contracts/solver/src/TestSolverExPost.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.25; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import { SolverBase } from "../SolverBase.sol"; +import { SolverBase } from "src/contracts/solver/SolverBase.sol"; // Flashbots opensource repo import { BlindBackrun } from "./BlindBackrun/BlindBackrun.sol"; diff --git a/src/contracts/types/SolverOperation.sol b/src/contracts/types/SolverOperation.sol index 7e6bf94b..ffd9392e 100644 --- a/src/contracts/types/SolverOperation.sol +++ b/src/contracts/types/SolverOperation.sol @@ -6,8 +6,8 @@ bytes32 constant SOLVER_TYPEHASH = keccak256( ); // NOTE: The calldata length of this SolverOperation struct is 608 bytes when the `data` field is excluded. This value -// is stored in the `_SOLVER_OP_BASE_CALLDATA` constant in Storage.sol and must be kept up-to-date with any changes to -// this struct. +// is stored in the `_SOLVER_OP_BASE_CALLDATA` constant in AtlasConstants.sol and must be kept up-to-date with any +// changes to this struct. struct SolverOperation { address from; // Solver address address to; // Atlas address diff --git a/test/AccountingMath.t.sol b/test/AccountingMath.t.sol index a9b26d18..69cb1263 100644 --- a/test/AccountingMath.t.sol +++ b/test/AccountingMath.t.sol @@ -5,68 +5,83 @@ import "forge-std/Test.sol"; import "src/contracts/libraries/AccountingMath.sol"; contract AccountingMathTest is Test { + uint256 DEFAULT_ATLAS_SURCHARGE_RATE = 1_000_000; // 10% + uint256 DEFAULT_BUNDLER_SURCHARGE_RATE = 1_000_000; // 10% + function testWithBundlerSurcharge() public { - assertEq(AccountingMath.withBundlerSurcharge(0), uint256(0)); - assertEq(AccountingMath.withBundlerSurcharge(1), uint256(1)); - assertEq(AccountingMath.withBundlerSurcharge(11), uint256(12)); - assertEq(AccountingMath.withBundlerSurcharge(100), uint256(110)); - assertEq(AccountingMath.withBundlerSurcharge(1e18), uint256(11e17)); + assertEq(AccountingMath.withSurcharge(0, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(0)); + assertEq(AccountingMath.withSurcharge(1, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(1)); + assertEq(AccountingMath.withSurcharge(11, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(12)); + assertEq(AccountingMath.withSurcharge(100, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(110)); + assertEq(AccountingMath.withSurcharge(1e18, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(11e17)); vm.expectRevert(); - AccountingMath.withBundlerSurcharge(type(uint256).max); + AccountingMath.withSurcharge(type(uint256).max, DEFAULT_BUNDLER_SURCHARGE_RATE); } function testWithoutBundlerSurcharge() public { - assertEq(AccountingMath.withoutBundlerSurcharge(0), uint256(0)); - assertEq(AccountingMath.withoutBundlerSurcharge(1), uint256(0)); - assertEq(AccountingMath.withoutBundlerSurcharge(12), uint256(10)); - assertEq(AccountingMath.withoutBundlerSurcharge(110), uint256(100)); - assertEq(AccountingMath.withoutBundlerSurcharge(11e17), uint256(1e18)); + assertEq(AccountingMath.withoutSurcharge(0, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(0)); + assertEq(AccountingMath.withoutSurcharge(1, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(0)); + assertEq(AccountingMath.withoutSurcharge(12, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(10)); + assertEq(AccountingMath.withoutSurcharge(110, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(100)); + assertEq(AccountingMath.withoutSurcharge(11e17, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(1e18)); vm.expectRevert(); - AccountingMath.withoutBundlerSurcharge(type(uint256).max); + AccountingMath.withoutSurcharge(type(uint256).max, DEFAULT_BUNDLER_SURCHARGE_RATE); } function testWithAtlasAndBundlerSurcharges() public { - assertEq(AccountingMath.withAtlasAndBundlerSurcharges(0), uint256(0)); - assertEq(AccountingMath.withAtlasAndBundlerSurcharges(1), uint256(1)); - assertEq(AccountingMath.withAtlasAndBundlerSurcharges(10), uint256(12)); - assertEq(AccountingMath.withAtlasAndBundlerSurcharges(100), uint256(120)); - assertEq(AccountingMath.withAtlasAndBundlerSurcharges(1e18), uint256(12e17)); + assertEq( + AccountingMath.withSurcharges(0, DEFAULT_ATLAS_SURCHARGE_RATE, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(0) + ); + assertEq( + AccountingMath.withSurcharges(1, DEFAULT_ATLAS_SURCHARGE_RATE, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(1) + ); + assertEq( + AccountingMath.withSurcharges(10, DEFAULT_ATLAS_SURCHARGE_RATE, DEFAULT_BUNDLER_SURCHARGE_RATE), uint256(12) + ); + assertEq( + AccountingMath.withSurcharges(100, DEFAULT_ATLAS_SURCHARGE_RATE, DEFAULT_BUNDLER_SURCHARGE_RATE), + uint256(120) + ); + assertEq( + AccountingMath.withSurcharges(1e18, DEFAULT_ATLAS_SURCHARGE_RATE, DEFAULT_BUNDLER_SURCHARGE_RATE), + uint256(12e17) + ); vm.expectRevert(); - AccountingMath.withAtlasAndBundlerSurcharges(type(uint256).max); + AccountingMath.withSurcharges(type(uint256).max, DEFAULT_ATLAS_SURCHARGE_RATE, DEFAULT_BUNDLER_SURCHARGE_RATE); } function testGetAtlasSurcharge() public { - assertEq(AccountingMath.getAtlasSurcharge(0), uint256(0)); - assertEq(AccountingMath.getAtlasSurcharge(10), uint256(1)); - assertEq(AccountingMath.getAtlasSurcharge(20), uint256(2)); - assertEq(AccountingMath.getAtlasSurcharge(30), uint256(3)); - assertEq(AccountingMath.getAtlasSurcharge(100), uint256(10)); - assertEq(AccountingMath.getAtlasSurcharge(1_000_000), uint256(100_000)); - assertEq(AccountingMath.getAtlasSurcharge(1e18), uint256(1e17)); + assertEq(AccountingMath.getSurcharge(0, DEFAULT_ATLAS_SURCHARGE_RATE), uint256(0)); + assertEq(AccountingMath.getSurcharge(10, DEFAULT_ATLAS_SURCHARGE_RATE), uint256(1)); + assertEq(AccountingMath.getSurcharge(20, DEFAULT_ATLAS_SURCHARGE_RATE), uint256(2)); + assertEq(AccountingMath.getSurcharge(30, DEFAULT_ATLAS_SURCHARGE_RATE), uint256(3)); + assertEq(AccountingMath.getSurcharge(100, DEFAULT_ATLAS_SURCHARGE_RATE), uint256(10)); + assertEq(AccountingMath.getSurcharge(1_000_000, DEFAULT_ATLAS_SURCHARGE_RATE), uint256(100_000)); + assertEq(AccountingMath.getSurcharge(1e18, DEFAULT_ATLAS_SURCHARGE_RATE), uint256(1e17)); vm.expectRevert(); - AccountingMath.getAtlasSurcharge(type(uint256).max); + AccountingMath.getSurcharge(type(uint256).max, DEFAULT_ATLAS_SURCHARGE_RATE); } function testSolverGasLimitScaledDown() public { assertEq(AccountingMath.solverGasLimitScaledDown(0, 100), uint256(0)); assertEq(AccountingMath.solverGasLimitScaledDown(50, 100), uint256(47)); // 50 * 10_000_000 / 10_500_000 assertEq(AccountingMath.solverGasLimitScaledDown(100, 200), uint256(95)); - + assertEq(AccountingMath.solverGasLimitScaledDown(200, 100), uint256(95)); assertEq(AccountingMath.solverGasLimitScaledDown(300, 200), uint256(190)); - + assertEq(AccountingMath.solverGasLimitScaledDown(100, 100), uint256(95)); assertEq(AccountingMath.solverGasLimitScaledDown(200, 200), uint256(190)); - - assertEq(AccountingMath.solverGasLimitScaledDown(1_000_000, 500_000), uint256(476_190)); // 500_000 * 10_000_000 / 10_500_000 - assertEq(AccountingMath.solverGasLimitScaledDown(1e18, 1e18), uint256(952380952380952380)); - + + assertEq(AccountingMath.solverGasLimitScaledDown(1_000_000, 500_000), uint256(476_190)); // 500_000 * 10_000_000 + // / 10_500_000 + assertEq(AccountingMath.solverGasLimitScaledDown(1e18, 1e18), uint256(952_380_952_380_952_380)); + vm.expectRevert(); assertEq(AccountingMath.solverGasLimitScaledDown(type(uint256).max, type(uint256).max), type(uint256).max); - + assertEq(AccountingMath.solverGasLimitScaledDown(1, 2), uint256(0)); // 1 * 10_000_000 / 10_500_000 assertEq(AccountingMath.solverGasLimitScaledDown(3, 3), uint256(2)); // 3 * 10_000_000 / 10_500_000 assertEq(AccountingMath.solverGasLimitScaledDown(5, 10), uint256(4)); // 5 * 10_000_000 / 10_500_000 - } } diff --git a/test/Atlas.t.sol b/test/BidFinding.t.sol similarity index 63% rename from test/Atlas.t.sol rename to test/BidFinding.t.sol index f64d010c..fb61ab6e 100644 --- a/test/Atlas.t.sol +++ b/test/BidFinding.t.sol @@ -20,38 +20,11 @@ import "src/contracts/types/LockTypes.sol"; import { LibSort } from "solady/utils/LibSort.sol"; -// These tests focus on the functions found in the Atlas.sol file -contract AtlasTest is BaseTest { - - function setUp_bidFindingIteration() public { - // super.setUp(); - - // vm.startPrank(payee); - // simulator = new Simulator(); - - // // Computes the addresses at which AtlasVerification will be deployed - // address expectedAtlasAddr = vm.computeCreateAddress(payee, vm.getNonce(payee) + 1); - // address expectedAtlasVerificationAddr = vm.computeCreateAddress(payee, vm.getNonce(payee) + 2); - // bytes32 salt = keccak256(abi.encodePacked(block.chainid, expectedAtlasAddr, "AtlasFactory 1.0")); - // ExecutionEnvironment execEnvTemplate = new ExecutionEnvironment{ salt: salt }(expectedAtlasAddr); - - // atlas = new MockAtlas({ - // escrowDuration: 64, - // verification: expectedAtlasVerificationAddr, - // simulator: address(simulator), - // executionTemplate: address(execEnvTemplate), - // surchargeRecipient: payee - // }); - // atlasVerification = new AtlasVerification(address(atlas)); - // simulator.setAtlas(address(atlas)); - // sorter = new Sorter(address(atlas)); - // vm.stopPrank(); - } - +contract BidFindingTest is BaseTest { function test_bidFindingIteration_sortingOrder() public pure { // Test order of bidsAndIndices after insertionSort - // 3 items. [200, 0, 100] --> [0, 100, 200] + // 3 items. [200, 0, 100] --> [0, 100, 200] uint256[] memory bidsAndIndices = new uint256[](3); bidsAndIndices[0] = 100; bidsAndIndices[1] = 0; @@ -80,7 +53,7 @@ contract AtlasTest is BaseTest { } function test_bidFindingIteration_packBidAndIndex() public pure { - uint256 bid = 12345; + uint256 bid = 12_345; uint256 index = 2; uint256 packed = _packBidAndIndex(bid, index); @@ -113,7 +86,6 @@ contract AtlasTest is BaseTest { assertEq(unpackedIndex, index); } - // Packs bid and index into a single uint256, replicates logic used in `_bidFindingIteration()` function _packBidAndIndex(uint256 bid, uint256 index) internal pure returns (uint256) { return uint256(bid << 16 | uint16(index)); @@ -127,29 +99,4 @@ contract AtlasTest is BaseTest { // uint256 solverOpsIndex = bidsAndIndices[i] & FIRST_16_BITS_MASK; index = packed & uint256(0xFFFF); } -} - -// MockAtlas exposes Atlas' internal functions for testing -contract MockAtlas is Atlas { - constructor( - uint256 _escrowDuration, - address _verification, - address _simulator, - address _surchargeRecipient, - address _l2GasCalculator, - address _executionTemplate - ) - Atlas(_escrowDuration, _verification, _simulator, _surchargeRecipient, _l2GasCalculator, _executionTemplate) - { } - - function bidFindingIteration( - DAppConfig calldata dConfig, - UserOperation calldata userOp, - SolverOperation[] calldata solverOps, - bytes memory returnData, - Context memory ctx - ) public returns (Context memory) { - _bidFindingIteration(ctx, dConfig, userOp, solverOps, returnData); - return ctx; - } } \ No newline at end of file diff --git a/test/DAppIntegration.t.sol b/test/DAppIntegration.t.sol index 7bbb5c08..b136b27c 100644 --- a/test/DAppIntegration.t.sol +++ b/test/DAppIntegration.t.sol @@ -16,6 +16,9 @@ contract MockDAppIntegration is DAppIntegration { } contract DAppIntegrationTest is Test { + uint256 DEFAULT_ATLAS_SURCHARGE_RATE = 1_000_000; // 10% + uint256 DEFAULT_BUNDLER_SURCHARGE_RATE = 1_000_000; // 10% + Atlas public atlas; MockDAppIntegration public dAppIntegration; DummyDAppControl public dAppControl; @@ -40,6 +43,8 @@ contract DAppIntegrationTest is Test { // Deploy the Atlas contract with correct parameters atlas = new Atlas({ escrowDuration: 64, + atlasSurchargeRate: DEFAULT_ATLAS_SURCHARGE_RATE, + bundlerSurchargeRate: DEFAULT_BUNDLER_SURCHARGE_RATE, verification: address(atlasVerification), simulator: address(0), executionTemplate: address(execEnvTemplate), diff --git a/test/Escrow.t.sol b/test/Escrow.t.sol index e789f019..cacbf804 100644 --- a/test/Escrow.t.sol +++ b/test/Escrow.t.sol @@ -143,7 +143,10 @@ contract EscrowTest is BaseTest { .withAllowAllocateValueFailure(true) // Allow the value allocation to fail .build() ); - executeHookCase(false, block.timestamp * 2, noError); + + (UserOperation memory userOp,,) = executeHookCase(block.timestamp * 2, noError); + bytes memory expectedInput = abi.encode(userOp); + assertEq(expectedInput, dAppControl.preOpsInputData(), "preOpsInputData should match expectedInput"); } // Ensure metacall reverts with the proper error when the preOps hook reverts. @@ -154,7 +157,8 @@ contract EscrowTest is BaseTest { .withReuseUserOp(true) // Allow metacall to revert .build() ); - executeHookCase(true, 0, AtlasErrors.PreOpsFail.selector); + dAppControl.setPreOpsShouldRevert(true); + executeHookCase(0, AtlasErrors.PreOpsFail.selector); } // Ensure the user operation executes successfully. To ensure the operation's returned data is as expected, we @@ -168,7 +172,9 @@ contract EscrowTest is BaseTest { .withAllowAllocateValueFailure(true) // Allow the value allocation to fail .build() ); - executeHookCase(false, block.timestamp * 3, noError); + executeHookCase(block.timestamp * 3, noError); + bytes memory expectedInput = abi.encode(block.timestamp * 3); + assertEq(expectedInput, dAppControl.userOpInputData(), "userOpInputData should match expectedInput"); } // Ensure metacall reverts with the proper error when the user operation reverts. @@ -178,7 +184,8 @@ contract EscrowTest is BaseTest { .withReuseUserOp(true) // Allow metacall to revert .build() ); - executeHookCase(true, 0, AtlasErrors.UserOpFail.selector); + dAppControl.setUserOpShouldRevert(true); + executeHookCase(0, AtlasErrors.UserOpFail.selector); } // Ensure metacall reverts with the proper error when the allocateValue hook reverts. @@ -191,7 +198,9 @@ contract EscrowTest is BaseTest { .withAllowAllocateValueFailure(false) // Do not allow the value allocation to fail .build() ); - executeHookCase(false, 1, AtlasErrors.AllocateValueFail.selector); + + dAppControl.setAllocateValueShouldRevert(true); + executeHookCase(1, AtlasErrors.AllocateValueFail.selector); } // Ensure the postOps hook is successfully called. No return data is expected from the postOps hook, so we do not @@ -202,7 +211,9 @@ contract EscrowTest is BaseTest { .withRequirePostOps(true) // Execute the postOps hook .build() ); - executeHookCase(false, 0, noError); + executeHookCase(0, noError); + bytes memory expectedInput = abi.encode(true, new bytes(0)); + assertEq(expectedInput, dAppControl.postOpsInputData(), "postOpsInputData should match expectedInput"); } // Ensure metacall reverts with the proper error when the postOps hook reverts. @@ -216,7 +227,8 @@ contract EscrowTest is BaseTest { .withAllowAllocateValueFailure(true) // Allow the value allocation to fail .build() ); - executeHookCase(false, 1, AtlasErrors.PostOpsFail.selector); + dAppControl.setPostOpsShouldRevert(true); + executeHookCase(1, AtlasErrors.PostOpsFail.selector); } // Ensure the allocateValue hook is successfully called. No return data is expected from the allocateValue hook, so @@ -228,35 +240,37 @@ contract EscrowTest is BaseTest { .withTrackUserReturnData(true) // Track the user operation's return data .build() ); + uint256 userOpArg = 321; - vm.prank(userEOA); - address executionEnvironment = atlas.createExecutionEnvironment(userEOA, address(dAppControl)); + executeHookCase(userOpArg, noError); - vm.expectEmit(false, false, false, true, executionEnvironment); - emit MEVPaymentSuccess(address(0), defaultBidAmount); - this.executeHookCase(false, 0, noError); + bytes memory expectedInput = abi.encode(address(0), defaultBidAmount, abi.encode(userOpArg)); + assertEq(expectedInput, dAppControl.allocateValueInputData(), "allocateValueInputData should match expectedInput"); } - function executeHookCase(bool hookShouldRevert, uint256 expectedHookReturnValue, bytes4 expectedError) public { + function executeHookCase(uint256 expectedHookReturnValue, bytes4 expectedError) public returns( + UserOperation memory userOp, + SolverOperation[] memory solverOps, + DAppOperation memory dappOp + ) { bool revertExpected = expectedError != noError; - UserOperation memory userOp = validUserOperation(address(dAppControl)) + userOp = validUserOperation(address(dAppControl)) .withData( abi.encodeWithSelector( dAppControl.userOperationCall.selector, - hookShouldRevert, expectedHookReturnValue ) ) .signAndBuild(address(atlasVerification), userPK); - SolverOperation[] memory solverOps = new SolverOperation[](1); + solverOps = new SolverOperation[](1); solverOps[0] = validSolverOperation(userOp) .withBidAmount(defaultBidAmount) .withData(abi.encode(expectedHookReturnValue)) .signAndBuild(address(atlasVerification), solverOnePK); - DAppOperation memory dappOp = validDAppOperation(userOp, solverOps).build(); + dappOp = validDAppOperation(userOp, solverOps).build(); if (revertExpected) { vm.expectRevert(expectedError); @@ -382,7 +396,7 @@ contract EscrowTest is BaseTest { ); UserOperation memory userOp = validUserOperation(address(dAppControl)) - .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, false, 1)) + .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, 1)) .signAndBuild(address(atlasVerification), userPK); SolverOperation[] memory solverOps = new SolverOperation[](1); @@ -391,6 +405,8 @@ contract EscrowTest is BaseTest { .signAndBuild(address(atlasVerification), solverOnePK); uint256 result = (1 << uint256(SolverOutcome.PreSolverFailed)); + dAppControl.setPreSolverShouldRevert(true); + executeSolverOperationCase(userOp, solverOps, false, false, result, true); } @@ -406,7 +422,7 @@ contract EscrowTest is BaseTest { ); UserOperation memory userOp = validUserOperation(address(dAppControl)) - .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, false, 1)) + .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, 1)) .signAndBuild(address(atlasVerification), userPK); SolverOperation[] memory solverOps = new SolverOperation[](1); @@ -415,6 +431,8 @@ contract EscrowTest is BaseTest { .signAndBuild(address(atlasVerification), solverOnePK); uint256 result = (1 << uint256(SolverOutcome.PostSolverFailed)); + dAppControl.setPostSolverShouldRevert(true); + executeSolverOperationCase(userOp, solverOps, true, false, result, true); } @@ -463,11 +481,6 @@ contract EscrowTest is BaseTest { .withBidAmount(bidAmount) .signAndBuild(address(atlasVerification), solverOnePK); - console.log("DApp control balance", address(gasSponsorControl).balance); - console.log("Solver balance", address(dummySolver).balance); - console.log("Bid amount (to trigger partial)", bidAmount); - console.log("Solver bonded amt", atlas.balanceOfBonded(solverOneEOA)); - uint256 expectedResult = 0; // Success expected executeSolverOperationCase(userOp, solverOps, true, true, expectedResult, false); } @@ -479,7 +492,6 @@ contract EscrowTest is BaseTest { uint256 solverOpValue = address(atlas).balance; assertTrue(solverOpValue > 0, "solverOpValue must be greater than 0"); - deal(address(solver), 10e18); // plenty of ETH to repay what solver owes (UserOperation memory userOp, SolverOperation[] memory solverOps) = executeSolverOperationInit(defaultCallConfig().build()); @@ -495,6 +507,67 @@ contract EscrowTest is BaseTest { assertTrue(success, "metacall should have succeeded"); } + function test_executeSolverOperation_ForwardReturnData_True() public { + // Checks that the solver CAN receive the data returned from the userOp phase + uint256 expectedDataValue = 123; + DummySolverContributor solver = new DummySolverContributor(address(atlas)); + assertEq(solver.forwardedData().length, 0, "solver forwardedData should start empty"); + deal(address(solver), 1 ether); // 1 ETH covers default bid (1) + 0.5 ETH gas cost + + (UserOperation memory userOp, SolverOperation[] memory solverOps) = executeSolverOperationInit( + defaultCallConfig() + .withTrackUserReturnData(true) + .withForwardReturnData(true) + .withRequireFulfillment(true) + .build() + ); + + userOp = validUserOperation(address(dAppControl)) + .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, expectedDataValue)) + .signAndBuild(address(atlasVerification), userPK); + + solverOps[0] = validSolverOperation(userOp) + .withSolver(address(solver)) + .withBidAmount(defaultBidAmount) + .signAndBuild(address(atlasVerification), solverOnePK); + + uint256 result = 0; // Success + executeSolverOperationCase(userOp, solverOps, true, true, result, false); + + uint256 forwardedData = abi.decode(solver.forwardedData(), (uint256)); + assertEq(forwardedData, expectedDataValue, "solver should have received the userOp data"); + } + + function test_executeSolverOperation_ForwardReturnData_False() public { + // Checks that the solver CANNOT receive the data returned from the userOp phase + uint256 dataValue = 123; + DummySolverContributor solver = new DummySolverContributor(address(atlas)); + assertEq(solver.forwardedData().length, 0, "solver forwardedData should start empty"); + deal(address(solver), 1 ether); // 1 ETH covers default bid (1) + 0.5 ETH gas cost + + (UserOperation memory userOp, SolverOperation[] memory solverOps) = executeSolverOperationInit( + defaultCallConfig() + .withTrackUserReturnData(true) + .withForwardReturnData(false) + .withRequireFulfillment(true) + .build() + ); + + userOp = validUserOperation(address(dAppControl)) + .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, dataValue)) + .signAndBuild(address(atlasVerification), userPK); + + solverOps[0] = validSolverOperation(userOp) + .withSolver(address(solver)) + .withBidAmount(defaultBidAmount) + .signAndBuild(address(atlasVerification), solverOnePK); + + uint256 result = 0; // Success + executeSolverOperationCase(userOp, solverOps, true, true, result, false); + assertEq(solver.forwardedData().length, 0, "solver forwardedData should still be empty"); + } + + function test_executeSolverOperation_solverOpWrapper_defaultCase() public { // Can't find a way to reach the default case (which is a good thing) vm.skip(true); @@ -507,7 +580,7 @@ contract EscrowTest is BaseTest { defaultAtlasWithCallConfig(callConfig); userOp = validUserOperation(address(dAppControl)) - .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, false, 0)) + .withData(abi.encodeWithSelector(dAppControl.userOperationCall.selector, 0)) .signAndBuild(address(atlasVerification), userPK); solverOps = new SolverOperation[](1); @@ -547,7 +620,7 @@ contract DummySolver { } function atlasSolverCall( - address solverOpFrom, + address /* solverOpFrom */, address executionEnvironment, address, uint256 bidAmount, @@ -567,7 +640,6 @@ contract DummySolver { if (address(this).balance >= bidAmount) { SafeTransferLib.safeTransferETH(executionEnvironment, bidAmount); } - if (bidAmount == noGasPayBack) { // Don't pay gas @@ -575,10 +647,6 @@ contract DummySolver { } else if (bidAmount == partialGasPayBack) { // Only pay half of shortfall owed - expect postSolverCall hook in DAppControl to pay the rest uint256 _shortfall = IAtlas(_atlas).shortfall(); - - console.log("Solver shortfall", _shortfall); - console.log("gas price", tx.gasprice); - IAtlas(_atlas).reconcile(_shortfall / 2); return; } @@ -591,31 +659,34 @@ contract DummySolver { } contract DummySolverContributor { - address private _atlas; + address private immutable ATLAS; + bytes public forwardedData; constructor(address atlas) { - _atlas = atlas; + ATLAS = atlas; } function atlasSolverCall( - address solverOpFrom, + address /* solverOpFrom */, address executionEnvironment, address, uint256 bidAmount, bytes calldata, - bytes calldata + bytes calldata userReturnData ) external payable { + if (userReturnData.length > 0) forwardedData = userReturnData; + // Pay bid if (address(this).balance >= bidAmount) { SafeTransferLib.safeTransferETH(executionEnvironment, bidAmount); } // Pay borrowed ETH + gas used - uint256 shortfall = IAtlas(_atlas).shortfall(); - IAtlas(_atlas).reconcile{value: shortfall}(0); + uint256 shortfall = IAtlas(ATLAS).shortfall(); + IAtlas(ATLAS).reconcile{value: shortfall}(0); return; } diff --git a/test/Factory.t.sol b/test/Factory.t.sol index 814db49c..9da4b498 100644 --- a/test/Factory.t.sol +++ b/test/Factory.t.sol @@ -47,6 +47,9 @@ contract MockFactory is Factory, Test { } contract FactoryTest is Test { + uint256 DEFAULT_ATLAS_SURCHARGE_RATE = 1_000_000; // 10% + uint256 DEFAULT_BUNDLER_SURCHARGE_RATE = 1_000_000; // 10% + Atlas public atlas; AtlasVerification public atlasVerification; MockFactory public mockFactory; @@ -66,6 +69,8 @@ contract FactoryTest is Test { vm.startPrank(deployer); atlas = new Atlas({ escrowDuration: 64, + atlasSurchargeRate: DEFAULT_ATLAS_SURCHARGE_RATE, + bundlerSurchargeRate: DEFAULT_BUNDLER_SURCHARGE_RATE, verification: expectedAtlasVerificationAddr, simulator: address(0), executionTemplate: address(execEnvTemplate), diff --git a/test/GasAccounting.t.sol b/test/GasAccounting.t.sol index 39f62bfb..daaea648 100644 --- a/test/GasAccounting.t.sol +++ b/test/GasAccounting.t.sol @@ -30,13 +30,15 @@ contract MockGasAccounting is TestAtlas, BaseTest { constructor( uint256 _escrowDuration, + uint256 _atlasSurchargeRate, + uint256 _bundlerSurchargeRate, address _verification, address _simulator, address _surchargeRecipient, address _l2GasCalculator, address _executionTemplate ) - TestAtlas(_escrowDuration, _verification, _simulator, _surchargeRecipient, _l2GasCalculator, _executionTemplate) + TestAtlas(_escrowDuration, _atlasSurchargeRate, _bundlerSurchargeRate, _verification, _simulator, _surchargeRecipient, _l2GasCalculator, _executionTemplate) { } ///////////////////////////////////////////////////////// @@ -172,14 +174,6 @@ contract MockGasAccounting is TestAtlas, BaseTest { function getFixedGasOffset() external pure returns (uint256) { return AccountingMath._FIXED_GAS_OFFSET; } - - function getAtlasSurchargeRate() external pure returns (uint256) { - return AccountingMath._ATLAS_SURCHARGE_RATE; - } - - function getBundlerSurchargeRate() external pure returns (uint256) { - return AccountingMath._BUNDLER_SURCHARGE_RATE; - } } contract MockGasCalculator is IL2GasCalculator, Test { @@ -212,6 +206,8 @@ contract GasAccountingTest is AtlasConstants, BaseTest { // Initialize MockGasAccounting mockGasAccounting = new MockGasAccounting( DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, address(atlasVerification), address(simulator), deployer, @@ -973,7 +969,7 @@ contract GasAccountingTest is AtlasConstants, BaseTest { (gasWaterMark + mockGasAccounting.getSolverBaseGasUsed() - gasleft()) * tx.gasprice + gasUsedOffset; mockGasAccounting.handleSolverAccounting(solverOp, gasWaterMark, result, false); - uint256 expectedWriteoffs = initialWriteoffs + AccountingMath.withAtlasAndBundlerSurcharges(gasUsed); + uint256 expectedWriteoffs = initialWriteoffs + AccountingMath.withSurcharges(gasUsed, DEFAULT_ATLAS_SURCHARGE_RATE, DEFAULT_BUNDLER_SURCHARGE_RATE); // Verify writeoffs have increased assertApproxEqRel( mockGasAccounting.getWriteoffs(), @@ -1110,6 +1106,8 @@ contract GasAccountingTest is AtlasConstants, BaseTest { IL2GasCalculator gasCalculator = new MockGasCalculator(); MockGasAccounting mockL2GasAccounting = new MockGasAccounting( DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, address(atlasVerification), address(simulator), deployer, diff --git a/test/Permit69.t.sol b/test/Permit69.t.sol index b5b47f4c..2262ad3b 100644 --- a/test/Permit69.t.sol +++ b/test/Permit69.t.sol @@ -22,7 +22,6 @@ import "src/contracts/types/LockTypes.sol"; import "src/contracts/types/DAppOperation.sol"; contract Permit69Test is BaseTest { - Context ctx; address mockUser; @@ -42,10 +41,12 @@ contract Permit69Test is BaseTest { address expectedFactoryAddr = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 2); bytes32 salt = keccak256(abi.encodePacked(block.chainid, expectedFactoryAddr)); ExecutionEnvironment execEnvTemplate = new ExecutionEnvironment{ salt: salt }(expectedFactoryAddr); - + vm.startPrank(deployer); mockAtlas = new MockAtlasForPermit69Tests({ _escrowDuration: 64, + _atlasSurchargeRate: DEFAULT_ATLAS_SURCHARGE_RATE, + _bundlerSurchargeRate: DEFAULT_BUNDLER_SURCHARGE_RATE, _verification: expectedAtlasVerificationAddr, _simulator: address(0), _executionTemplate: address(execEnvTemplate), @@ -97,9 +98,7 @@ contract Permit69Test is BaseTest { vm.prank(solverOneEOA); vm.expectRevert(AtlasErrors.InvalidEnvironment.selector); - mockAtlas.transferUserERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); } function testTransferUserERC20RevertsIfLockStateNotValid() public { @@ -115,27 +114,21 @@ contract Permit69Test is BaseTest { // Uninitialized vm.expectRevert(AtlasErrors.InvalidLockState.selector); - mockAtlas.transferUserERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); // AllocateValue phase = ExecutionPhase.AllocateValue; mockAtlas.setContext(ctx); mockAtlas.setPhase(phase); vm.expectRevert(AtlasErrors.InvalidLockState.selector); - mockAtlas.transferUserERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); // Releasing phase = ExecutionPhase.Uninitialized; mockAtlas.setContext(ctx); mockAtlas.setPhase(phase); vm.expectRevert(AtlasErrors.InvalidLockState.selector); - mockAtlas.transferUserERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); vm.stopPrank(); } @@ -157,9 +150,7 @@ contract Permit69Test is BaseTest { WETH.approve(address(mockAtlas), wethTransferred); vm.prank(mockExecutionEnvAddress); - mockAtlas.transferUserERC20( - WETH_ADDRESS, solverOneEOA, wethTransferred, mockUser, mockDAppControl - ); + mockAtlas.transferUserERC20(WETH_ADDRESS, solverOneEOA, wethTransferred, mockUser, mockDAppControl); assertEq(WETH.balanceOf(mockUser), userWethBefore - wethTransferred, "User did not lose WETH"); assertEq(WETH.balanceOf(solverOneEOA), solverWethBefore + wethTransferred, "Solver did not gain WETH"); @@ -173,9 +164,7 @@ contract Permit69Test is BaseTest { vm.prank(solverOneEOA); vm.expectRevert(AtlasErrors.InvalidEnvironment.selector); - mockAtlas.transferDAppERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); } function testTransferDAppERC20RevertsIfLockStateNotValid() public { @@ -189,33 +178,25 @@ contract Permit69Test is BaseTest { ExecutionPhase phase = ExecutionPhase.Uninitialized; mockAtlas.setPhase(phase); vm.expectRevert(AtlasErrors.InvalidLockState.selector); - mockAtlas.transferDAppERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); // UserOperation phase = ExecutionPhase.UserOperation; mockAtlas.setPhase(phase); vm.expectRevert(AtlasErrors.InvalidLockState.selector); - mockAtlas.transferDAppERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); // SolverOperations phase = ExecutionPhase.SolverOperation; mockAtlas.setPhase(phase); vm.expectRevert(AtlasErrors.InvalidLockState.selector); - mockAtlas.transferDAppERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); // Releasing phase = ExecutionPhase.Uninitialized; mockAtlas.setPhase(phase); vm.expectRevert(AtlasErrors.InvalidLockState.selector); - mockAtlas.transferDAppERC20( - WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl - ); + mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, 10e18, mockUser, mockDAppControl); vm.stopPrank(); } @@ -236,9 +217,7 @@ contract Permit69Test is BaseTest { WETH.approve(address(mockAtlas), wethTransferred); vm.prank(mockExecutionEnvAddress); - mockAtlas.transferDAppERC20( - WETH_ADDRESS, solverOneEOA, wethTransferred, mockUser, mockDAppControl - ); + mockAtlas.transferDAppERC20(WETH_ADDRESS, solverOneEOA, wethTransferred, mockUser, mockDAppControl); assertEq(WETH.balanceOf(mockDAppControl), dAppWethBefore - wethTransferred, "DApp did not lose WETH"); assertEq(WETH.balanceOf(solverOneEOA), solverWethBefore + wethTransferred, "Solver did not gain WETH"); @@ -279,7 +258,7 @@ contract Permit69Test is BaseTest { function testConstantValueOfSafeDAppTransfer() public { // FIXME: fix before merging spearbit-reaudit branch vm.skip(true); - + string memory expectedBitMapString = "0000111010100000"; // Safe phases for dApp transfers are PreOps, AllocateValue, and DAppOperation // preOpsPhaseSafe = 0000 0000 0010 0000 @@ -293,8 +272,8 @@ contract Permit69Test is BaseTest { // verificationPhaseSafe = 0000 0100 0000 0000 uint8 verificationPhaseSafe = uint8(ExecutionPhase.PostOps); - uint16 expectedSafeDAppTransferBitMap = - preOpsPhaseSafe | preSolverOpsPhaseSafe | postSolverOpsPhaseSafe | allocateValuePhaseSafe | verificationPhaseSafe; + uint16 expectedSafeDAppTransferBitMap = preOpsPhaseSafe | preSolverOpsPhaseSafe | postSolverOpsPhaseSafe + | allocateValuePhaseSafe | verificationPhaseSafe; assertEq( mockAtlas.getSafeDAppTransfer(), @@ -323,13 +302,24 @@ contract Permit69Test is BaseTest { contract MockAtlasForPermit69Tests is Atlas { constructor( uint256 _escrowDuration, + uint256 _atlasSurchargeRate, + uint256 _bundlerSurchargeRate, address _verification, address _simulator, address _surchargeRecipient, address _l2GasCalculator, address _executionTemplate ) - Atlas(_escrowDuration, _verification, _simulator, _surchargeRecipient, _l2GasCalculator, _executionTemplate) + Atlas( + _escrowDuration, + _atlasSurchargeRate, + _bundlerSurchargeRate, + _verification, + _simulator, + _surchargeRecipient, + _l2GasCalculator, + _executionTemplate + ) { } // Declared in SafetyLocks.sol in the canonical Atlas system @@ -354,10 +344,7 @@ contract MockAtlasForPermit69Tests is Atlas { _environment = _activeEnvironment; } - function setLock( - address _activeEnvironment, - uint32 callConfig - ) public { + function setLock(address _activeEnvironment, uint32 callConfig) public { _setLock({ activeEnvironment: _activeEnvironment, callConfig: callConfig, @@ -376,12 +363,20 @@ contract MockAtlasForPermit69Tests is Atlas { uint32 callConfig ) public - returns (address executionEnvironment) + returns (address executionEnvironment) { return _getOrCreateExecutionEnvironment(user, control, callConfig); } - function verifyUserControlExecutionEnv(address sender, address user, address control, uint32 callConfig) internal view returns (bool) + function verifyUserControlExecutionEnv( + address sender, + address user, + address control, + uint32 callConfig + ) + internal + view + returns (bool) { return _verifyUserControlExecutionEnv(sender, user, control, callConfig); } diff --git a/test/SafetyLocks.t.sol b/test/SafetyLocks.t.sol index 5fab4b49..0cef1f73 100644 --- a/test/SafetyLocks.t.sol +++ b/test/SafetyLocks.t.sol @@ -11,7 +11,7 @@ import "src/contracts/types/ConfigTypes.sol"; import "src/contracts/types/LockTypes.sol"; contract MockSafetyLocks is SafetyLocks { - constructor() SafetyLocks(0, address(0), address(0), address(0), address(0)) { } + constructor() SafetyLocks(0, 1000000, 1000000, address(0), address(0), address(0), address(0)) { } function initializeLock( address executionEnvironment, diff --git a/test/Storage.t.sol b/test/Storage.t.sol index 536cbe69..a3e07beb 100644 --- a/test/Storage.t.sol +++ b/test/Storage.t.sol @@ -11,8 +11,6 @@ import { BaseTest } from "test/base/BaseTest.t.sol"; contract StorageTest is BaseTest { using stdStorage for StdStorage; - uint256 constant DEFAULT_ATLAS_SURCHARGE_RATE = 1_000_000; // out of 10_000_000 = 10% - uint256 constant DEFAULT_BUNDLER_SURCHARGE_RATE = 1_000_000; // out of 10_000_000 = 10% uint256 constant DEFAULT_SCALE = 10_000_000; // out of 10_000_000 = 100% uint256 constant DEFAULT_FIXED_GAS_OFFSET = 85_000; @@ -32,7 +30,9 @@ contract StorageTest is BaseTest { assertEq(atlas.decimals(), 18, "decimals set incorrectly"); assertEq(atlas.ATLAS_SURCHARGE_RATE(), DEFAULT_ATLAS_SURCHARGE_RATE, "ATLAS_SURCHARGE_RATE set incorrectly"); - assertEq(atlas.BUNDLER_SURCHARGE_RATE(), DEFAULT_BUNDLER_SURCHARGE_RATE, "BUNDLER_SURCHARGE_RATE set incorrectly"); + assertEq( + atlas.BUNDLER_SURCHARGE_RATE(), DEFAULT_BUNDLER_SURCHARGE_RATE, "BUNDLER_SURCHARGE_RATE set incorrectly" + ); assertEq(atlas.SCALE(), DEFAULT_SCALE, "SCALE set incorrectly"); assertEq(atlas.FIXED_GAS_OFFSET(), DEFAULT_FIXED_GAS_OFFSET, "FIXED_GAS_OFFSET set incorrectly"); } @@ -45,7 +45,7 @@ contract StorageTest is BaseTest { vm.deal(userEOA, depositAmount); vm.prank(userEOA); - atlas.deposit{value: depositAmount}(); + atlas.deposit{ value: depositAmount }(); assertEq(atlas.totalSupply(), startTotalSupply + depositAmount, "totalSupply did not increase correctly"); } @@ -56,14 +56,20 @@ contract StorageTest is BaseTest { vm.deal(userEOA, depositAmount); vm.prank(userEOA); - atlas.depositAndBond{value: depositAmount}(depositAmount); + atlas.depositAndBond{ value: depositAmount }(depositAmount); assertEq(atlas.bondedTotalSupply(), depositAmount, "bondedTotalSupply did not increase correctly"); } function test_storage_view_accessData() public { uint256 depositAmount = 1e18; - (uint256 bonded, uint256 lastAccessedBlock, uint256 auctionWins, uint256 auctionFails, uint256 totalGasValueUsed) = atlas.accessData(userEOA); + ( + uint256 bonded, + uint256 lastAccessedBlock, + uint256 auctionWins, + uint256 auctionFails, + uint256 totalGasValueUsed + ) = atlas.accessData(userEOA); assertEq(bonded, 0, "user bonded should start as 0"); assertEq(lastAccessedBlock, 0, "user lastAccessedBlock should start as 0"); @@ -73,7 +79,7 @@ contract StorageTest is BaseTest { vm.deal(userEOA, depositAmount); vm.prank(userEOA); - atlas.depositAndBond{value: depositAmount}(depositAmount); + atlas.depositAndBond{ value: depositAmount }(depositAmount); (bonded, lastAccessedBlock, auctionWins, auctionFails, totalGasValueUsed) = atlas.accessData(userEOA); @@ -96,7 +102,15 @@ contract StorageTest is BaseTest { } function test_storage_view_solverOpHashes() public { - MockStorage mockStorage = new MockStorage(DEFAULT_ESCROW_DURATION, address(0), address(0), address(0), address(0)); + MockStorage mockStorage = new MockStorage( + DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, + address(0), + address(0), + address(0), + address(0) + ); bytes32 testHash = keccak256(abi.encodePacked("test")); assertEq(mockStorage.solverOpHashes(testHash), false, "solverOpHashes[testHash] not false"); mockStorage.setSolverOpHash(testHash); @@ -104,7 +118,15 @@ contract StorageTest is BaseTest { } function test_storage_view_cumulativeSurcharge() public { - MockStorage mockStorage = new MockStorage(DEFAULT_ESCROW_DURATION, address(0), address(0), address(0), address(0)); + MockStorage mockStorage = new MockStorage( + DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, + address(0), + address(0), + address(0), + address(0) + ); assertEq(mockStorage.cumulativeSurcharge(), 0, "cumulativeSurcharge not 0"); mockStorage.setCumulativeSurcharge(100); assertEq(mockStorage.cumulativeSurcharge(), 100, "cumulativeSurcharge not 100"); @@ -157,7 +179,15 @@ contract StorageTest is BaseTest { function test_storage_transient_solverLockData() public { // MockStorage just used here to access AtlasConstants - MockStorage mockStorage = new MockStorage(DEFAULT_ESCROW_DURATION, address(0), address(0), address(0), address(0)); + MockStorage mockStorage = new MockStorage( + DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, + address(0), + address(0), + address(0), + address(0) + ); (address currentSolver, bool calledBack, bool fulfilled) = atlas.solverLockData(); assertEq(currentSolver, address(0), "currentSolver should start at 0"); @@ -180,7 +210,8 @@ contract StorageTest is BaseTest { assertEq(calledBack, true, "calledBack should still be true"); assertEq(fulfilled, true, "fulfilled should be true"); - testSolverLock = mockStorage.SOLVER_CALLED_BACK_MASK() | mockStorage.SOLVER_FULFILLED_MASK() | uint256(uint160(userEOA)); + testSolverLock = + mockStorage.SOLVER_CALLED_BACK_MASK() | mockStorage.SOLVER_FULFILLED_MASK() | uint256(uint160(userEOA)); atlas.setSolverLock(testSolverLock); (currentSolver, calledBack, fulfilled) = atlas.solverLockData(); @@ -247,7 +278,15 @@ contract StorageTest is BaseTest { } function test_storage_transient_solverTo() public { - MockStorage mockStorage = new MockStorage(DEFAULT_ESCROW_DURATION, address(0), address(0), address(0), address(0)); + MockStorage mockStorage = new MockStorage( + DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, + address(0), + address(0), + address(0), + address(0) + ); assertEq(mockStorage.solverTo(), address(0), "solverTo should start at 0"); mockStorage.setSolverTo(userEOA); @@ -258,7 +297,15 @@ contract StorageTest is BaseTest { } function test_storage_transient_activeEnvironment() public { - MockStorage mockStorage = new MockStorage(DEFAULT_ESCROW_DURATION, address(0), address(0), address(0), address(0)); + MockStorage mockStorage = new MockStorage( + DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, + address(0), + address(0), + address(0), + address(0) + ); assertEq(mockStorage.activeEnvironment(), address(0), "activeEnvironment should start at 0"); mockStorage.setLock(address(1), 0, 0); @@ -269,7 +316,15 @@ contract StorageTest is BaseTest { } function test_storage_transient_activeCallConfig() public { - MockStorage mockStorage = new MockStorage(DEFAULT_ESCROW_DURATION, address(0), address(0), address(0), address(0)); + MockStorage mockStorage = new MockStorage( + DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, + address(0), + address(0), + address(0), + address(0) + ); assertEq(mockStorage.activeCallConfig(), 0, "activeCallConfig should start at 0"); mockStorage.setLock(address(0), 1, 0); @@ -280,7 +335,15 @@ contract StorageTest is BaseTest { } function test_storage_transient_phase() public { - MockStorage mockStorage = new MockStorage(DEFAULT_ESCROW_DURATION, address(0), address(0), address(0), address(0)); + MockStorage mockStorage = new MockStorage( + DEFAULT_ESCROW_DURATION, + DEFAULT_ATLAS_SURCHARGE_RATE, + DEFAULT_BUNDLER_SURCHARGE_RATE, + address(0), + address(0), + address(0), + address(0) + ); assertEq(mockStorage.phase(), 0, "phase should start at 0"); mockStorage.setLock(address(0), 0, 1); @@ -293,19 +356,28 @@ contract StorageTest is BaseTest { // To test solverOpHashes() and cumulativeSurcharge() view function contract MockStorage is Storage { - // For solverLockData test uint256 public constant SOLVER_CALLED_BACK_MASK = _SOLVER_CALLED_BACK_MASK; uint256 public constant SOLVER_FULFILLED_MASK = _SOLVER_FULFILLED_MASK; constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator ) - Storage(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator) + Storage( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator + ) { } function setSolverOpHash(bytes32 opHash) public { @@ -376,4 +448,4 @@ contract MockStorage is Storage { function getDeposits() external view returns (uint256) { return deposits(); } -} \ No newline at end of file +} diff --git a/test/base/BaseTest.t.sol b/test/base/BaseTest.t.sol index a4347929..f4b40272 100644 --- a/test/base/BaseTest.t.sol +++ b/test/base/BaseTest.t.sol @@ -51,6 +51,8 @@ contract BaseTest is Test { IERC20 DAI = IERC20(DAI_ADDRESS); uint256 DEFAULT_ESCROW_DURATION = 64; + uint256 DEFAULT_ATLAS_SURCHARGE_RATE = 1_000_000; // 10% + uint256 DEFAULT_BUNDLER_SURCHARGE_RATE = 1_000_000; // 10% uint256 MAINNET_FORK_BLOCK = 17_441_786; function setUp() public virtual { @@ -80,6 +82,8 @@ contract BaseTest is Test { atlas = new TestAtlas({ escrowDuration: DEFAULT_ESCROW_DURATION, + atlasSurchargeRate: DEFAULT_ATLAS_SURCHARGE_RATE, + bundlerSurchargeRate: DEFAULT_BUNDLER_SURCHARGE_RATE, verification: expectedAtlasVerificationAddr, simulator: address(simulator), executionTemplate: address(execEnvTemplate), diff --git a/test/base/DummyDAppControl.sol b/test/base/DummyDAppControl.sol index e74653c1..b33af3cc 100644 --- a/test/base/DummyDAppControl.sol +++ b/test/base/DummyDAppControl.sol @@ -14,6 +14,20 @@ library CallConfigBuilder { } contract DummyDAppControl is DAppControl { + bool public preOpsShouldRevert; + bool public userOpShouldRevert; + bool public preSolverShouldRevert; + bool public postSolverShouldRevert; + bool public allocateValueShouldRevert; + bool public postOpsShouldRevert; + + bytes public preOpsInputData; + bytes public userOpInputData; + bytes public preSolverInputData; + bytes public postSolverInputData; + bytes public allocateValueInputData; + bytes public postOpsInputData; + event MEVPaymentSuccess(address bidToken, uint256 bidAmount); constructor( @@ -31,38 +45,37 @@ contract DummyDAppControl is DAppControl { function _checkUserOperation(UserOperation memory) internal pure virtual override { } function _preOpsCall(UserOperation calldata userOp) internal virtual override returns (bytes memory) { - if (userOp.data.length == 0) { - return new bytes(0); - } + bool shouldRevert = DummyDAppControl(CONTROL).preOpsShouldRevert(); + require(!shouldRevert, "_preOpsCall revert requested"); + + DummyDAppControl(CONTROL).setInputData(abi.encode(userOp), 0); + console.logBytes(abi.encode(userOp)); - (bool success, bytes memory data) = address(userOp.dapp).call(userOp.data); - require(success, "_preOpsCall reverted"); + if (userOp.data.length == 0) return new bytes(0); + + (, bytes memory data) = address(userOp.dapp).call(userOp.data); return data; } - function _postOpsCall(bool, bytes calldata data) internal pure virtual override { - if (data.length == 0) return; - - (bool shouldRevert) = abi.decode(data, (bool)); + function _postOpsCall(bool solved, bytes calldata data) internal virtual override { + bool shouldRevert = DummyDAppControl(CONTROL).postOpsShouldRevert(); require(!shouldRevert, "_postOpsCall revert requested"); - } - function _preSolverCall(SolverOperation calldata, bytes calldata returnData) internal view virtual override { - if (returnData.length == 0) { - return; - } + DummyDAppControl(CONTROL).setInputData(abi.encode(solved, data), 5); + } - (bool shouldRevert) = abi.decode(returnData, (bool)); + function _preSolverCall(SolverOperation calldata solverOp, bytes calldata returnData) internal virtual override { + bool shouldRevert = DummyDAppControl(CONTROL).preSolverShouldRevert(); require(!shouldRevert, "_preSolverCall revert requested"); - } - function _postSolverCall(SolverOperation calldata, bytes calldata returnData) internal pure virtual override { - if (returnData.length == 0) { - return; - } + DummyDAppControl(CONTROL).setInputData(abi.encode(solverOp, returnData), 2); + } - (bool shouldRevert) = abi.decode(returnData, (bool)); + function _postSolverCall(SolverOperation calldata solverOp, bytes calldata returnData) internal virtual override { + bool shouldRevert = DummyDAppControl(CONTROL).postSolverShouldRevert(); require(!shouldRevert, "_postSolverCall revert requested"); + + DummyDAppControl(CONTROL).setInputData(abi.encode(solverOp, returnData), 3); } function _allocateValueCall( @@ -74,13 +87,12 @@ contract DummyDAppControl is DAppControl { virtual override { - if (data.length == 0) { - return; - } - - (bool shouldRevert) = abi.decode(data, (bool)); + bool shouldRevert = DummyDAppControl(CONTROL).allocateValueShouldRevert(); require(!shouldRevert, "_allocateValueCall revert requested"); - emit MEVPaymentSuccess(bidToken, winningAmount); + + DummyDAppControl(CONTROL).setInputData(abi.encode(bidToken, winningAmount, data), 4); + + // emit MEVPaymentSuccess(bidToken, winningAmount); } function getBidValue(SolverOperation calldata solverOp) public view virtual override returns (uint256) { @@ -93,8 +105,53 @@ contract DummyDAppControl is DAppControl { // Custom functions // **************************************** - function userOperationCall(bool shouldRevert, uint256 returnValue) public pure returns (uint256) { + function userOperationCall(uint256 returnValue) public returns (uint256) { + bool shouldRevert = DummyDAppControl(CONTROL).userOpShouldRevert(); require(!shouldRevert, "userOperationCall revert requested"); + + DummyDAppControl(CONTROL).setInputData(abi.encode(returnValue), 1); + return returnValue; } + + // Revert settings + + function setPreOpsShouldRevert(bool _preOpsShouldRevert) public { + preOpsShouldRevert = _preOpsShouldRevert; + } + + function setUserOpShouldRevert(bool _userOpShouldRevert) public { + userOpShouldRevert = _userOpShouldRevert; + } + + function setPreSolverShouldRevert(bool _preSolverShouldRevert) public { + preSolverShouldRevert = _preSolverShouldRevert; + } + + function setPostSolverShouldRevert(bool _postSolverShouldRevert) public { + postSolverShouldRevert = _postSolverShouldRevert; + } + + function setAllocateValueShouldRevert(bool _allocateValueShouldRevert) public { + allocateValueShouldRevert = _allocateValueShouldRevert; + } + + function setPostOpsShouldRevert(bool _postOpsShouldRevert) public { + postOpsShouldRevert = _postOpsShouldRevert; + } + + // Called by the EE to save input data for testing after the metacall ends + function setInputData( + bytes memory inputData, + uint256 hook // 0: preOps, 1: userOp, 2: preSolver, 3: postSolver, 4: allocateValue, 5: postOps + ) + public + { + if (hook == 0) preOpsInputData = inputData; + if (hook == 1) userOpInputData = inputData; + if (hook == 2) preSolverInputData = inputData; + if (hook == 3) postSolverInputData = inputData; + if (hook == 4) allocateValueInputData = inputData; + if (hook == 5) postOpsInputData = inputData; + } } diff --git a/test/base/TestAtlas.sol b/test/base/TestAtlas.sol index 4e325dc4..27c3da92 100644 --- a/test/base/TestAtlas.sol +++ b/test/base/TestAtlas.sol @@ -9,13 +9,24 @@ import "src/contracts/atlas/Atlas.sol"; contract TestAtlas is Atlas { constructor( uint256 escrowDuration, + uint256 atlasSurchargeRate, + uint256 bundlerSurchargeRate, address verification, address simulator, address initialSurchargeRecipient, address l2GasCalculator, address executionTemplate ) - Atlas(escrowDuration, verification, simulator, initialSurchargeRecipient, l2GasCalculator, executionTemplate) + Atlas( + escrowDuration, + atlasSurchargeRate, + bundlerSurchargeRate, + verification, + simulator, + initialSurchargeRecipient, + l2GasCalculator, + executionTemplate + ) { } // Public functions to expose internal transient helpers for testing diff --git a/test/helpers/DummyDAppControlBuilder.sol b/test/helpers/DummyDAppControlBuilder.sol index 5aa25c46..d9eaafd2 100644 --- a/test/helpers/DummyDAppControlBuilder.sol +++ b/test/helpers/DummyDAppControlBuilder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { DummyDAppControl } from "../base/DummyDAppControl.sol"; +import { DummyDAppControl } from "test/base/DummyDAppControl.sol"; import { CallConfig } from "src/contracts/types/ConfigTypes.sol"; import { AtlasVerification } from "src/contracts/atlas/AtlasVerification.sol"; diff --git a/test/libraries/CallBits.t.sol b/test/libraries/CallBits.t.sol index 3f84e299..ffc093ca 100644 --- a/test/libraries/CallBits.t.sol +++ b/test/libraries/CallBits.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import { CallBits } from "src/contracts/libraries/CallBits.sol"; import "src/contracts/types/UserOperation.sol"; -import "../base/TestUtils.sol"; +import "test/base/TestUtils.sol"; contract CallBitsTest is Test { using CallBits for uint32; diff --git a/test/libraries/CallVerification.t.sol b/test/libraries/CallVerification.t.sol index 73e95bc8..66522419 100644 --- a/test/libraries/CallVerification.t.sol +++ b/test/libraries/CallVerification.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import { CallVerification } from "src/contracts/libraries/CallVerification.sol"; import "src/contracts/types/UserOperation.sol"; -import "../base/TestUtils.sol"; +import "test/base/TestUtils.sol"; contract CallVerificationTest is Test { using CallVerification for UserOperation; diff --git a/test/libraries/SafetyBits.t.sol b/test/libraries/SafetyBits.t.sol index 26b7650f..52ba6c97 100644 --- a/test/libraries/SafetyBits.t.sol +++ b/test/libraries/SafetyBits.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import { SafetyBits } from "src/contracts/libraries/SafetyBits.sol"; import "src/contracts/types/LockTypes.sol"; -import "../base/TestUtils.sol"; +import "test/base/TestUtils.sol"; import { CallBits } from "src/contracts/libraries/CallBits.sol";