TW
Tripwire
Find contracts. Test them. Review real vulns.

Analysis Run

019d5666-721c-732b-98f2-8fef0dd24f32

failed
Chain ID
1
Address
Block Number
Created
Sat, Apr 4, 2026 2:50 AM
Review Summary

What survived the funnel

Open this run to answer one question: is this real attacker-relevant behavior with real impact, or just normal contract plumbing?

Confirmed
0
Validated
0
Source
Verified
Review Backing
Present
Best current signal
Untrusted DELEGATECALL target reachable
crit 0 high 0 proxy context implementation known
Review Checklist
1. Read the findings table first. Ignore raw jobs until the result looks real.
2. Confirm attacker control, not just the existence of a sink.
3. Confirm money or privilege impact.
4. Keep it if the behavior is real. Kill it if this is normal proxy, admin, or upgrade plumbing.

Processing Jobs

Status & attempts
ID Type Status Attempts Heartbeat
42869 artifact.fetch complete 1 / 3 1 week ago
42870 proxy.resolve complete 1 / 3 1 week ago
42871 tool.cast_disasm complete 1 / 3 1 week ago
42872 analysis.bundle complete 1 / 3 1 week ago
42873 capability.graph complete 1 / 3 1 week ago
42874 detector.run complete 1 / 3 1 week ago
42875 validation.fork complete 1 / 3 1 week ago
42978 report.consolidate complete 1 / 6 1 week ago
42979 tool.slither failed 2 / 2 1 week ago

Artifact

Runtime bytecode: available
Creation TX:
Deployer:
Proxy: yes confirmed
Verified Source: yes
Proxy evidence
{
    "method": "etherscan+onchain",
    "status": "confirmed",
    "etherscan_hint": {
        "source": "etherscan",
        "etherscan": {
            "proxy_flag": true,
            "implementation": "0x00000000d681e85e5783588f87a9573cb97eda01"
        }
    },
    "implementation": "0x00000000d681e85e5783588f87a9573cb97eda01",
    "onchain_method": "eip1967"
}

Slither

tool.slither
Status
failed slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-q3mbt37d) 'forge config --json' running 'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-q3mbt37d) Traceback (most recent call last): File "/var/www/tripwire/.venv/bin/slither", line 10, in <module> sys.exit(main()) ~~~~^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 776, in main main_impl(all_detector_classes=detectors, all_printer_classes=printers) ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl ) = process_all(filename, args, detector_classes, printer_classes) ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 96, in process_all compilations = compile_all(target, **vars(args)) File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 722, in compile_all compilations.append(CryticCompile(target, **kwargs)) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 211, in __init__ self._compile(**kwargs) ~~~~~~~~~~~~~^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 633, in _compile self._platform.compile(self, **kwargs) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/foundry.py", line 102, in compile hardhat_like_parsing( ~~~~~~~~~~~~~~~~~~~~^ crytic_compile, str(self._target), build_directory, str(self._project_root) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/hardhat.py", line 52, in hardhat_like_parsing raise InvalidCompilation(txt) crytic_compile.platform.exceptions.InvalidCompilation: Compilation failed. Can you run build command? /tmp/slither-q3mbt37d/out/build-info is not a directory.
Attempts
2 / 2
Findings
0
Solc
View stderr
Traceback (most recent call last):
  File "/var/www/tripwire/worker/worker.py", line 198, in main
    payload = dispatch_job(job_type, job_id, job_meta, run_data, config, laravel_client)
  File "/var/www/tripwire/worker/worker.py", line 114, in dispatch_job
    return slither_run.run(job_id, job_meta, run_data, config, log, laravel_client)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/worker/jobs/slither_run.py", line 1084, in run
    stdout_text, stderr_text, result_json = _run_slither(
                                            ~~~~~~~~~~~~^
        workspace,
        ^^^^^^^^^^
    ...<4 lines>...
        target_path=target_path,
        ^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/var/www/tripwire/worker/jobs/slither_run.py", line 958, in _run_slither
    raise RuntimeError(f"slither failed (exit {result.returncode}): {stderr_text or stdout_text}")
RuntimeError: slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-q3mbt37d)
'forge config --json' running
'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-q3mbt37d)
Traceback (most recent call last):
  File "/var/www/tripwire/.venv/bin/slither", line 10, in <module>
    sys.exit(main())
             ~~~~^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 776, in main
    main_impl(all_detector_classes=detectors, all_printer_classes=printers)
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
        ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 96, in process_all
    compilations = compile_all(target, **vars(args))
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 722, in compile_all
    compilations.append(CryticCompile(target, **kwargs))
                        ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 211, in __init__
    self._compile(**kwargs)
    ~~~~~~~~~~~~~^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 633, in _compile
    self._platform.compile(self, **kwargs)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/foundry.py", line 102, in compile
    hardhat_like_parsing(
    ~~~~~~~~~~~~~~~~~~~~^
        crytic_compile, str(self._target), build_directory, str(self._project_root)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/hardhat.py", line 52, in hardhat_like_parsing
    raise InvalidCompilation(txt)
crytic_compile.platform.exceptions.InvalidCompilation: Compilation failed. Can you run build command?
/tmp/slither-q3mbt37d/out/build-info is not a directory.

Codex

report.consolidate
Job Status
complete
Attempts
1 / 6
Findings
2
No findings?
no
Summary
Read `AUDIT_BUNDLE.txt` in full. Found 2 material issues in the implementation: proxy upgrades can strand state because storage slots are version/name-dependent, and CoWSwap orders do not bound `feeAmount`, allowing approved sell tokens to be overspent beyond `params.amountIn`.
View stdout
{"no_findings":false,"summary":"Read `AUDIT_BUNDLE.txt` in full. Found 2 material issues in the implementation: proxy upgrades can strand state because storage slots are version/name-dependent, and CoWSwap orders do not bound `feeAmount`, allowing approved sell tokens to be overspent beyond `params.amountIn`.","findings":[{"severity":"high","category":"upgradeability","title":"Version-dependent storage slots can brick the proxy and strand assets on upgrade","exploit_scenario":"The proxy points at this implementation today. If the proxy admin later upgrades to a new `SwapModule` implementation deployed with a different `name_` or `version_`, the immutables `_swapModuleStorageSlot` and `_mellowACLStorageSlot` change. After the upgrade, `subvault()`, `oracle()`, multipliers, and supported-role bookkeeping are read from empty storage. Because `subvault` has no setter and `initialize` is one-shot, the proxy can no longer satisfy `onlySubvault`, leaving assets held by the proxy permanently stuck.","recommendation":"Do not derive live storage slots from constructor-time version/name inputs for proxy state. Use fixed ERC-7201/ERC-1967-style slots that remain identical across implementation versions, or enforce that every upgrade reuses the exact same slot constants.","evidence":"`SlotLibrary.getSlot` mixes `contractName`, `name`, and `version` into the slot seed. `MellowACL` and `SwapModule` cache those derived slots in immutables during construction and use them for all reads/writes of first-party state, so a new implementation with different constructor args silently points the proxy at a different storage region.","locations":["src/libraries/SlotLibrary.sol:13","src/permissions/MellowACL.sol:11","src/permissions/MellowACL.sol:58","src/utils/SwapModule.sol:37","src/utils/SwapModule.sol:62","src/utils/SwapModule.sol:197","src/utils/SwapModule.sol:306"]},{"severity":"medium","category":"access_control","title":"`createLimitOrder` does not constrain CoWSwap `feeAmount`, so CALLER_ROLE can spend more than `params.amountIn`","exploit_scenario":"A compromised or over-scoped `CALLER_ROLE` can first call `setCowswapApproval(asset, type(uint256).max)`, then create a pre-signed CoWSwap order with `sellAmount == params.amountIn` but an arbitrarily large `feeAmount`. The order passes `checkParams` and `checkCowswapOrder`, yet CoWSwap settlement will pull `sellAmount + feeAmount` from the module. The extra sell token is outside the min-out/slippage checks, so approved balances can be drained far beyond the amount the module intended to trade.","recommendation":"Reject orders with nonzero `feeAmount`, or explicitly include fee in the validated parameters and in the balance/allowance accounting. If partial fills are not needed, also require `order.partiallyFillable == false` and scope approval to the exact live exposure instead of a reusable allowance.","evidence":"`checkCowswapOrder` validates token addresses, `sellAmount`, `buyAmount`, deadline, kind, and balance modes, but never checks `order.feeAmount` (or `order.partiallyFillable`). Separately, `setCowswapApproval` allows arbitrary allowance to `cowswapVaultRelayer`, so settlement can legally transfer more sell token than `params.amountIn`.","locations":["src/utils/SwapModule.sol:137","src/utils/SwapModule.sol:150","src/utils/SwapModule.sol:153","src/utils/SwapModule.sol:277","src/utils/SwapModule.sol:286"]}]}
View stderr
OpenAI Codex v0.118.0 (research preview)
--------
workdir: /tmp/codex-ky1rccd0
model: gpt-5.4
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: none
session id: 019d569a-dc65-7581-b53e-c6f89aa6ec96
--------
user
Context: The user requested the proxy at 0xc95b806ac073df930014ac476d26c8ad918f14e0.
Audit the implementation at 0x00000000d681e85e5783588f87a9573cb97eda01.
Findings should refer to the implementation unless explicitly about proxy mechanics.

You are a security auditor. Audit this Solidity codebase for exploitable vulnerabilities.
Focus on: reentrancy, access control, upgradeability/proxy issues, signature validation, oracle manipulation,
accounting/invariant breaks, external call patterns, unchecked return values, and dangerous delegatecall.

Scope rules:
- You MUST fully read `AUDIT_BUNDLE.txt` before concluding. It contains the complete contents of all first-party Solidity sources in this workspace.
- Treat `lib/` and `@openzeppelin/` as third-party dependencies; skip them unless there is a concrete misuse angle in first-party code.
- If there are only dependency files and no first-party sources, set no_findings=true and explain in summary.

Output MUST be valid JSON matching the provided schema.
- If you find nothing meaningful: set no_findings=true and findings=[].
- Otherwise: set no_findings=false and include findings with severity/category/title and file/line locations when possible.

Output constraints (IMPORTANT):
- severity MUST be exactly one of: info, low, medium, high, critical (lowercase).
- category MUST be exactly one of: access_control, reentrancy, math, oracle, upgradeability, auth, other.
Keep summary short and concrete.
warning: Codex could not find system bubblewrap on PATH. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime.
exec
/bin/bash -lc pwd in /tmp/codex-ky1rccd0
 succeeded in 0ms:
/tmp/codex-ky1rccd0

exec
/bin/bash -lc 'rg --files' in /tmp/codex-ky1rccd0
 succeeded in 0ms:
AUDIT_BUNDLE.txt
codex-output-schema.json
src/permissions/MellowACL.sol
src/libraries/TransferLibrary.sol
src/libraries/SlotLibrary.sol
src/interfaces/permissions/IMellowACL.sol
src/utils/SwapModule.sol
src/interfaces/factories/IFactoryEntity.sol
src/interfaces/external/aave/IAaveOracle.sol
src/interfaces/utils/ISwapModule.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol
lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
lib/contracts/src/contracts/GPv2Settlement.sol
lib/contracts/src/contracts/GPv2VaultRelayer.sol
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol
lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol
lib/openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlEnumerableUpgradeable.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol
lib/contracts/src/contracts/interfaces/GPv2Authentication.sol
lib/contracts/src/contracts/interfaces/GPv2EIP1271.sol
lib/contracts/src/contracts/interfaces/IERC20.sol
lib/contracts/src/contracts/interfaces/IVault.sol
lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol
lib/contracts/src/contracts/mixins/GPv2Signing.sol
lib/contracts/src/contracts/mixins/ReentrancyGuard.sol
lib/contracts/src/contracts/mixins/StorageAccessible.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlEnumerable.sol
lib/contracts/src/contracts/libraries/GPv2SafeERC20.sol
lib/contracts/src/contracts/libraries/GPv2Transfer.sol
lib/contracts/src/contracts/libraries/GPv2Interaction.sol
lib/contracts/src/contracts/libraries/GPv2Order.sol
lib/contracts/src/contracts/libraries/SafeCast.sol
lib/contracts/src/contracts/libraries/SafeMath.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
lib/contracts/src/contracts/libraries/GPv2Trade.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Arrays.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Comparators.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Panic.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol

exec
/bin/bash -lc 'wc -l AUDIT_BUNDLE.txt' in /tmp/codex-ky1rccd0
 succeeded in 0ms:
658 AUDIT_BUNDLE.txt

exec
/bin/bash -lc "sed -n '1,260p' AUDIT_BUNDLE.txt" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
This file concatenates the full contents of first-party Solidity sources for auditing.
Do not treat dependency code under lib/ or @openzeppelin/ as first-party.

================================================================================
FILE: src/interfaces/external/aave/IAaveOracle.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IAaveOracle {
    function getAssetPrice(address asset) external view returns (uint256);
}

================================================================================
FILE: src/interfaces/factories/IFactoryEntity.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

/// @title IFactoryEntity
interface IFactoryEntity {
    /// @notice Initializes the factory-created entity with arbitrary initialization data.
    /// @param initParams The initialization parameters.
    function initialize(bytes calldata initParams) external;

    /// @notice Emitted once the entity has been initialized.
    /// @param initParams The initialization parameters.
    event Initialized(bytes initParams);
}

================================================================================
FILE: src/interfaces/permissions/IMellowACL.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";

/// @notice Interface for the MellowACL contract, which extends OpenZeppelin's AccessControlEnumerable
/// @dev Adds tracking of which roles are actively in use (i.e., assigned to at least one address)
interface IMellowACL is IAccessControlEnumerable {
    /// @notice Storage layout used to track actively assigned roles
    struct MellowACLStorage {
        EnumerableSet.Bytes32Set supportedRoles; // Set of roles that have at least one assigned member
    }

    /// @notice Returns the total number of unique roles that are currently assigned
    function supportedRoles() external view returns (uint256);

    /// @notice Returns the role at the specified index in the set of active roles
    /// @param index Index within the supported role set
    /// @return role The bytes32 identifier of the role
    function supportedRoleAt(uint256 index) external view returns (bytes32);

    /// @notice Checks whether a given role is currently active (i.e., has at least one member)
    /// @param role The bytes32 identifier of the role to check
    /// @return isActive True if the role has any members assigned
    function hasSupportedRole(bytes32 role) external view returns (bool);

    /// @notice Emitted when a new role is granted for the first time
    event RoleAdded(bytes32 indexed role);

    /// @notice Emitted when a role loses its last member
    event RoleRemoved(bytes32 indexed role);
}

================================================================================
FILE: src/interfaces/utils/ISwapModule.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../external/aave/IAaveOracle.sol";

import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";

import {GPv2Settlement} from "@cowswap/contracts/GPv2Settlement.sol";
import {GPv2Order} from "@cowswap/contracts/libraries/GPv2Order.sol";

import "../factories/IFactoryEntity.sol";

interface ISwapModule is IFactoryEntity {
    // Errors

    error Forbidden(string reason);
    error ZeroValue();

    // Structs

    struct SwapModuleStorage {
        address subvault;
        address oracle;
        uint256 defaultMultiplier;
        mapping(address => mapping(address => uint256)) customMultiplier;
    }

    struct Params {
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint256 minAmountOut;
        uint256 deadline;
    }

    // View functions

    function MIN_MULTIPLIER() external view returns (uint256);
    function MAX_MULTIPLIER() external view returns (uint256);
    function BASE_MULTIPLIER() external view returns (uint256);

    function TOKEN_IN_ROLE() external view returns (bytes32);
    function TOKEN_OUT_ROLE() external view returns (bytes32);
    function ROUTER_ROLE() external view returns (bytes32);
    function CALLER_ROLE() external view returns (bytes32);
    function SET_SLIPPAGE_ROLE() external view returns (bytes32);

    function cowswapSettlement() external view returns (address);
    function cowswapVaultRelayer() external view returns (address);
    function weth() external view returns (address);

    function subvault() external view returns (address);

    function oracle() external view returns (address);

    function checkMultiplier(uint256 multiplier) external pure;

    function defaultMultiplier() external view returns (uint256);

    function customMultiplier(address tokenIn, address tokenOut) external view returns (uint256);

    function evaluate(address tokenIn, address tokenOut, uint256 amountIn) external view returns (uint256);

    function checkParams(Params calldata params) external view;

    function checkCowswapOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
        external
        view;

    // Mutable functions

    function setOracle(address oracle_) external;

    function setDefaultMultiplier(uint256 multiplier) external;

    function setCustomMultiplier(address tokenIn, address tokenOut, uint256 multiplier) external;

    function pushAssets(address asset, uint256 value) external payable;

    function pullAssets(address asset, uint256 value) external;

    function swap(Params calldata params, address router, bytes calldata data)
        external
        returns (bytes memory response);

    function setCowswapApproval(address asset, uint256 amount) external;

    function createLimitOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
        external;

    function invalidateOrder(bytes calldata orderUid) external;

    // Events

    event AssetsPulled(address indexed asset, uint256 value);

    event AssetsPushed(address indexed asset, uint256 value);

    event DefaultMultiplierSet(uint256 indexed multiplier);

    event CustomMultiplierSet(address indexed tokenIn, address indexed tokenOut, uint256 indexed multiplier);

    event CowswapApprovalSet(address indexed asset, uint256 amount);

    event LimitOrderCreated(Params params, bytes orderUid);

    event LimitOrderInvalidated(bytes orderUid);

    event Swap(Params params, address router, uint256 amountOut);

    event OracleSet(address indexed oracle);
}

================================================================================
FILE: src/libraries/SlotLibrary.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

/// @title SlotLibrary
/// @notice Library for computing deterministic and collision-resistant storage slots
/// @dev Used to generate unique storage slots for upgradeable modules using string identifiers
library SlotLibrary {
    /// @notice Computes a unique storage slot based on the module's identifiers
    /// @param contractName Logical contract/module name (e.g., "ShareModule")
    /// @param name Human-readable instance name (e.g., "Mellow")
    /// @param version Version number for the module configuration
    /// @return A bytes32 value representing the derived storage slot
    function getSlot(string memory contractName, string memory name, uint256 version) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                uint256(keccak256(abi.encodePacked("mellow.flexible-vaults.storage.", contractName, name, version))) - 1
            )
        ) & ~bytes32(uint256(0xff));
    }
}

================================================================================
FILE: src/libraries/TransferLibrary.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/// @title TransferLibrary
/// @notice Library for unified handling of native ETH and ERC20 asset transfers.
/// @dev Provides safe and abstracted methods for sending and receiving both ETH and ERC20 tokens.
///
/// # ETH Convention
/// Uses the constant `ETH = 0xEeee...EeE` to distinguish native ETH from ERC20 tokens.
library TransferLibrary {
    using SafeERC20 for IERC20;

    /// @notice Error thrown when `msg.value` does not match expected ETH amount
    error InvalidValue();

    /// @dev Placeholder address used to represent native ETH transfers
    address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /// @notice Safely sends assets (ETH or ERC20) to a recipient
    /// @param asset Address of the asset to send (use `ETH` constant for native ETH)
    /// @param to Recipient address
    /// @param assets Amount of assets to send
    /// @dev Uses `Address.sendValue` for ETH and `safeTransfer` for ERC20
    function sendAssets(address asset, address to, uint256 assets) internal {
        if (asset == ETH) {
            Address.sendValue(payable(to), assets);
        } else {
            IERC20(asset).safeTransfer(to, assets);
        }
    }

    /// @notice Safely receives assets (ETH or ERC20) from a sender
    /// @param asset Address of the asset to receive (use `ETH` constant for native ETH)
    /// @param from Sender address (only used for ERC20)
    /// @param assets Amount of assets expected to receive
    /// @dev Reverts if `msg.value` is incorrect for ETH or uses `safeTransferFrom` for ERC20
    function receiveAssets(address asset, address from, uint256 assets) internal {
        if (asset == ETH) {
            if (msg.value != assets) {
                revert InvalidValue();
            }
        } else {
            IERC20(asset).safeTransferFrom(from, address(this), assets);
        }
    }

exec
/bin/bash -lc "sed -n '261,520p' AUDIT_BUNDLE.txt" in /tmp/codex-ky1rccd0
 succeeded in 0ms:

    /// @notice Returns the balance of an account for a given asset
    /// @param asset Address of the asset to check the balance of (use `ETH` constant for native ETH)
    /// @param account Address of the account to check the balance of
    /// @return Balance of the account for the given asset
    function balanceOf(address asset, address account) internal view returns (uint256) {
        if (asset == ETH) {
            return account.balance;
        } else {
            return IERC20(asset).balanceOf(account);
        }
    }
}

================================================================================
FILE: src/permissions/MellowACL.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/permissions/IMellowACL.sol";

import "../libraries/SlotLibrary.sol";

abstract contract MellowACL is IMellowACL, AccessControlEnumerableUpgradeable {
    using EnumerableSet for EnumerableSet.Bytes32Set;

    bytes32 private immutable _mellowACLStorageSlot;

    constructor(string memory name_, uint256 version_) {
        _mellowACLStorageSlot = SlotLibrary.getSlot("MellowACL", name_, version_);
        _disableInitializers();
    }

    // View functions

    /// @inheritdoc IMellowACL
    function supportedRoles() external view returns (uint256) {
        return _mellowACLStorage().supportedRoles.length();
    }

    /// @inheritdoc IMellowACL
    function supportedRoleAt(uint256 index) external view returns (bytes32) {
        return _mellowACLStorage().supportedRoles.at(index);
    }

    /// @inheritdoc IMellowACL
    function hasSupportedRole(bytes32 role) external view returns (bool) {
        return _mellowACLStorage().supportedRoles.contains(role);
    }

    // Internal functions

    function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
        if (super._grantRole(role, account)) {
            if (_mellowACLStorage().supportedRoles.add(role)) {
                emit RoleAdded(role);
            }
            return true;
        }
        return false;
    }

    function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
        if (super._revokeRole(role, account)) {
            if (getRoleMemberCount(role) == 0) {
                _mellowACLStorage().supportedRoles.remove(role);
                emit RoleRemoved(role);
            }
            return true;
        }
        return false;
    }

    function _mellowACLStorage() private view returns (MellowACLStorage storage $) {
        bytes32 slot = _mellowACLStorageSlot;
        assembly {
            $.slot := slot
        }
    }
}

================================================================================
FILE: src/utils/SwapModule.sol
================================================================================
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/utils/ISwapModule.sol";

import "../libraries/TransferLibrary.sol";
import "../permissions/MellowACL.sol";

contract SwapModule is ISwapModule, MellowACL, ReentrancyGuardUpgradeable {
    using SafeERC20 for IERC20;

    /// @inheritdoc ISwapModule
    uint256 public constant MIN_MULTIPLIER = 0.9e8;
    /// @inheritdoc ISwapModule
    uint256 public constant MAX_MULTIPLIER = 1.1e8;
    /// @inheritdoc ISwapModule
    uint256 public constant BASE_MULTIPLIER = 1e8;

    /// @inheritdoc ISwapModule
    bytes32 public constant TOKEN_IN_ROLE = keccak256("utils.SwapModule.TOKEN_IN_ROLE");
    /// @inheritdoc ISwapModule
    bytes32 public constant TOKEN_OUT_ROLE = keccak256("utils.SwapModule.TOKEN_OUT_ROLE");
    /// @inheritdoc ISwapModule
    bytes32 public constant ROUTER_ROLE = keccak256("utils.SwapModule.ROUTER_ROLE");
    /// @inheritdoc ISwapModule
    bytes32 public constant CALLER_ROLE = keccak256("utils.SwapModule.CALLER_ROLE");
    /// @inheritdoc ISwapModule
    bytes32 public constant SET_SLIPPAGE_ROLE = keccak256("utils.SwapModule.SET_SLIPPAGE_ROLE");

    /// @inheritdoc ISwapModule
    address public immutable cowswapSettlement;
    /// @inheritdoc ISwapModule
    address public immutable cowswapVaultRelayer;
    /// @inheritdoc ISwapModule
    address public immutable weth;

    bytes32 private immutable _swapModuleStorageSlot;

    constructor(
        string memory name_,
        uint256 version_,
        address cowswapSettlement_,
        address cowswapVaultRelayer_,
        address weth_
    ) MellowACL(name_, version_) {
        cowswapSettlement = cowswapSettlement_;
        cowswapVaultRelayer = cowswapVaultRelayer_;
        weth = weth_;
        _swapModuleStorageSlot = SlotLibrary.getSlot("SwapModule", name_, version_);
    }

    // View functions

    modifier onlySubvault() {
        if (_msgSender() != subvault()) {
            revert Forbidden("msg.sender != subvault");
        }
        _;
    }

    /// @inheritdoc ISwapModule
    function subvault() public view returns (address) {
        return _swapModuleStorage().subvault;
    }

    /// @inheritdoc ISwapModule
    function oracle() public view returns (address) {
        return _swapModuleStorage().oracle;
    }

    /// @inheritdoc ISwapModule
    function checkMultiplier(uint256 multiplier) public pure {
        if (multiplier < MIN_MULTIPLIER || multiplier > MAX_MULTIPLIER) {
            revert Forbidden("multiplier out of expected range");
        }
    }

    /// @inheritdoc ISwapModule
    function defaultMultiplier() public view returns (uint256) {
        return _swapModuleStorage().defaultMultiplier;
    }

    /// @inheritdoc ISwapModule
    function customMultiplier(address tokenIn, address tokenOut) public view returns (uint256) {
        return _swapModuleStorage().customMultiplier[tokenIn][tokenOut];
    }

    /// @inheritdoc ISwapModule
    function evaluate(address tokenIn, address tokenOut, uint256 amountIn) public view returns (uint256) {
        tokenIn = tokenIn == TransferLibrary.ETH ? weth : tokenIn;
        tokenOut = tokenOut == TransferLibrary.ETH ? weth : tokenOut;
        uint8 decimalsIn = IERC20Metadata(tokenIn).decimals();
        uint8 decimalsOut = IERC20Metadata(tokenOut).decimals();
        IAaveOracle oracle_ = IAaveOracle(oracle());
        uint256 tokenInPrice = oracle_.getAssetPrice(tokenIn);
        uint256 tokenOutPrice = oracle_.getAssetPrice(tokenOut);
        if (decimalsIn == decimalsOut) {
            return Math.mulDiv(amountIn, tokenInPrice, tokenOutPrice);
        } else if (decimalsIn < decimalsOut) {
            return Math.mulDiv(amountIn, tokenInPrice * 10 ** (decimalsOut - decimalsIn), tokenOutPrice);
        } else {
            return Math.mulDiv(amountIn, tokenInPrice, tokenOutPrice * 10 ** (decimalsIn - decimalsOut));
        }
    }

    /// @inheritdoc ISwapModule
    function checkParams(Params calldata params) public view {
        if (!hasRole(TOKEN_IN_ROLE, params.tokenIn)) {
            revert Forbidden("tokenIn");
        }
        if (!hasRole(TOKEN_OUT_ROLE, params.tokenOut)) {
            revert Forbidden("tokenOut");
        }
        if (TransferLibrary.balanceOf(params.tokenIn, address(this)) < params.amountIn || params.amountIn == 0) {
            revert Forbidden("amountIn");
        }
        SwapModuleStorage storage $ = _swapModuleStorage();
        uint256 multiplier = $.customMultiplier[params.tokenIn][params.tokenOut];
        if (multiplier == 0) {
            multiplier = $.defaultMultiplier;
        }
        uint256 oracleMinAmount =
            Math.mulDiv(evaluate(params.tokenIn, params.tokenOut, params.amountIn), multiplier, BASE_MULTIPLIER);

        if (params.minAmountOut < oracleMinAmount) {
            revert Forbidden("minAmountOut < oracleMinAmount");
        }
        if (params.deadline < block.timestamp) {
            revert Forbidden("deadline");
        }
        if (params.tokenIn == params.tokenOut) {
            revert Forbidden("tokenIn == tokenOut");
        }
    }

    /// @inheritdoc ISwapModule
    function checkCowswapOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
        public
        view
    {
        if (params.tokenIn != address(order.sellToken) || params.tokenIn == TransferLibrary.ETH) {
            revert Forbidden("sellToken");
        }
        if (params.tokenOut != address(order.buyToken) || params.tokenOut == TransferLibrary.ETH) {
            revert Forbidden("buyToken");
        }
        if (address(this) != order.receiver) {
            revert Forbidden("receiver");
        }
        if (params.amountIn != order.sellAmount) {
            revert Forbidden("amountIn != sellAmount");
        }
        if (params.minAmountOut != order.buyAmount) {
            revert Forbidden("minAmountOut != buyAmount");
        }
        if (params.deadline != order.validTo) {
            revert Forbidden("deadline != validTo");
        }
        if (order.kind != GPv2Order.KIND_SELL) {
            revert Forbidden("kind != KIND_SELL");
        }
        if (order.sellTokenBalance != GPv2Order.BALANCE_ERC20) {
            revert Forbidden("sellTokenBalance != BALANCE_ERC20");
        }
        if (order.buyTokenBalance != GPv2Order.BALANCE_ERC20) {
            revert Forbidden("buyTokenBalance != BALANCE_ERC20");
        }

        bytes memory calculatedOrderUid = new bytes(56);
        GPv2Order.packOrderUidParams(
            calculatedOrderUid,
            GPv2Order.hash(order, GPv2Settlement(payable(cowswapSettlement)).domainSeparator()),
            address(this),
            order.validTo
        );

exec
/bin/bash -lc "sed -n '521,760p' AUDIT_BUNDLE.txt" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
        if (keccak256(orderUid) != keccak256(calculatedOrderUid)) {
            revert Forbidden("orderUid != calculatedOrderUid");
        }
    }

    // Mutable functions

    /// @inheritdoc IFactoryEntity
    function initialize(bytes calldata data) external initializer {
        (
            address admin,
            address subvault_,
            address oracle_,
            uint256 defaultMultiplier_,
            address[] memory holders,
            bytes32[] memory roles
        ) = abi.decode(data, (address, address, address, uint256, address[], bytes32[]));
        if (admin == address(0) || subvault_ == address(0) || oracle_ == address(0)) {
            revert ZeroValue();
        }
        checkMultiplier(defaultMultiplier_);
        SwapModuleStorage storage $ = _swapModuleStorage();

        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        $.subvault = subvault_;
        $.oracle = oracle_;
        $.defaultMultiplier = defaultMultiplier_;
        for (uint256 i = 0; i < holders.length; i++) {
            if (holders[i] == address(0) || roles[i] == bytes32(0)) {
                revert ZeroValue();
            }
            _grantRole(roles[i], holders[i]);
        }
        emit Initialized(data);
    }

    /// @inheritdoc ISwapModule
    function setOracle(address oracle_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (oracle_ == address(0)) {
            revert ZeroValue();
        }
        _swapModuleStorage().oracle = oracle_;
        emit OracleSet(oracle_);
    }

    /// @inheritdoc ISwapModule
    function setDefaultMultiplier(uint256 multiplier) external onlyRole(SET_SLIPPAGE_ROLE) {
        checkMultiplier(multiplier);
        _swapModuleStorage().defaultMultiplier = multiplier;
        emit DefaultMultiplierSet(multiplier);
    }

    /// @inheritdoc ISwapModule
    function setCustomMultiplier(address tokenIn, address tokenOut, uint256 multiplier)
        external
        onlyRole(SET_SLIPPAGE_ROLE)
    {
        checkMultiplier(multiplier);
        _swapModuleStorage().customMultiplier[tokenIn][tokenOut] = multiplier;
        emit CustomMultiplierSet(tokenIn, tokenOut, multiplier);
    }

    /// @inheritdoc ISwapModule
    function pushAssets(address asset, uint256 value) external payable onlySubvault {
        TransferLibrary.receiveAssets(asset, _msgSender(), value);
        emit AssetsPushed(asset, value);
    }

    /// @inheritdoc ISwapModule
    function pullAssets(address asset, uint256 value) external onlySubvault {
        TransferLibrary.sendAssets(asset, _msgSender(), value);
        emit AssetsPulled(asset, value);
    }

    /// @inheritdoc ISwapModule
    function swap(Params calldata params, address router, bytes calldata data)
        external
        nonReentrant
        onlyRole(CALLER_ROLE)
        returns (bytes memory response)
    {
        checkParams(params);
        if (!hasRole(ROUTER_ROLE, router)) {
            revert Forbidden("router");
        }
        uint256 balanceBefore = TransferLibrary.balanceOf(params.tokenOut, address(this));
        if (params.tokenIn != TransferLibrary.ETH) {
            IERC20(params.tokenIn).forceApprove(router, params.amountIn);
            response = Address.functionCall(router, data);
            IERC20(params.tokenIn).forceApprove(router, 0);
        } else {
            response = Address.functionCallWithValue(router, data, params.amountIn);
        }
        uint256 amountOut = TransferLibrary.balanceOf(params.tokenOut, address(this)) - balanceBefore;
        if (amountOut < params.minAmountOut) {
            revert Forbidden("amountOut < minAmountOut");
        }
        emit Swap(params, router, amountOut);
    }

    /// @inheritdoc ISwapModule
    function setCowswapApproval(address asset, uint256 amount) external onlyRole(CALLER_ROLE) {
        if (asset == TransferLibrary.ETH || !hasRole(TOKEN_IN_ROLE, asset)) {
            revert Forbidden("asset");
        }
        IERC20(asset).forceApprove(cowswapVaultRelayer, amount);
        emit CowswapApprovalSet(asset, amount);
    }

    /// @inheritdoc ISwapModule
    function createLimitOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
        external
        onlyRole(CALLER_ROLE)
    {
        checkParams(params);
        checkCowswapOrder(params, order, orderUid);
        GPv2Settlement(payable(cowswapSettlement)).setPreSignature(orderUid, true);
        emit LimitOrderCreated(params, orderUid);
    }

    /// @inheritdoc ISwapModule
    function invalidateOrder(bytes calldata orderUid) external onlyRole(CALLER_ROLE) {
        GPv2Settlement(payable(cowswapSettlement)).invalidateOrder(orderUid);
        emit LimitOrderInvalidated(orderUid);
    }

    receive() external payable {}

    // Internal functions

    function _swapModuleStorage() internal view returns (SwapModuleStorage storage $) {
        bytes32 slot = _swapModuleStorageSlot;
        assembly {
            $.slot := slot
        }
    }
}


exec
/bin/bash -lc "nl -ba src/utils/SwapModule.sol | sed -n '1,260p'" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
     1	// SPDX-License-Identifier: BUSL-1.1
     2	pragma solidity 0.8.25;
     3	
     4	import "../interfaces/utils/ISwapModule.sol";
     5	
     6	import "../libraries/TransferLibrary.sol";
     7	import "../permissions/MellowACL.sol";
     8	
     9	contract SwapModule is ISwapModule, MellowACL, ReentrancyGuardUpgradeable {
    10	    using SafeERC20 for IERC20;
    11	
    12	    /// @inheritdoc ISwapModule
    13	    uint256 public constant MIN_MULTIPLIER = 0.9e8;
    14	    /// @inheritdoc ISwapModule
    15	    uint256 public constant MAX_MULTIPLIER = 1.1e8;
    16	    /// @inheritdoc ISwapModule
    17	    uint256 public constant BASE_MULTIPLIER = 1e8;
    18	
    19	    /// @inheritdoc ISwapModule
    20	    bytes32 public constant TOKEN_IN_ROLE = keccak256("utils.SwapModule.TOKEN_IN_ROLE");
    21	    /// @inheritdoc ISwapModule
    22	    bytes32 public constant TOKEN_OUT_ROLE = keccak256("utils.SwapModule.TOKEN_OUT_ROLE");
    23	    /// @inheritdoc ISwapModule
    24	    bytes32 public constant ROUTER_ROLE = keccak256("utils.SwapModule.ROUTER_ROLE");
    25	    /// @inheritdoc ISwapModule
    26	    bytes32 public constant CALLER_ROLE = keccak256("utils.SwapModule.CALLER_ROLE");
    27	    /// @inheritdoc ISwapModule
    28	    bytes32 public constant SET_SLIPPAGE_ROLE = keccak256("utils.SwapModule.SET_SLIPPAGE_ROLE");
    29	
    30	    /// @inheritdoc ISwapModule
    31	    address public immutable cowswapSettlement;
    32	    /// @inheritdoc ISwapModule
    33	    address public immutable cowswapVaultRelayer;
    34	    /// @inheritdoc ISwapModule
    35	    address public immutable weth;
    36	
    37	    bytes32 private immutable _swapModuleStorageSlot;
    38	
    39	    constructor(
    40	        string memory name_,
    41	        uint256 version_,
    42	        address cowswapSettlement_,
    43	        address cowswapVaultRelayer_,
    44	        address weth_
    45	    ) MellowACL(name_, version_) {
    46	        cowswapSettlement = cowswapSettlement_;
    47	        cowswapVaultRelayer = cowswapVaultRelayer_;
    48	        weth = weth_;
    49	        _swapModuleStorageSlot = SlotLibrary.getSlot("SwapModule", name_, version_);
    50	    }
    51	
    52	    // View functions
    53	
    54	    modifier onlySubvault() {
    55	        if (_msgSender() != subvault()) {
    56	            revert Forbidden("msg.sender != subvault");
    57	        }
    58	        _;
    59	    }
    60	
    61	    /// @inheritdoc ISwapModule
    62	    function subvault() public view returns (address) {
    63	        return _swapModuleStorage().subvault;
    64	    }
    65	
    66	    /// @inheritdoc ISwapModule
    67	    function oracle() public view returns (address) {
    68	        return _swapModuleStorage().oracle;
    69	    }
    70	
    71	    /// @inheritdoc ISwapModule
    72	    function checkMultiplier(uint256 multiplier) public pure {
    73	        if (multiplier < MIN_MULTIPLIER || multiplier > MAX_MULTIPLIER) {
    74	            revert Forbidden("multiplier out of expected range");
    75	        }
    76	    }
    77	
    78	    /// @inheritdoc ISwapModule
    79	    function defaultMultiplier() public view returns (uint256) {
    80	        return _swapModuleStorage().defaultMultiplier;
    81	    }
    82	
    83	    /// @inheritdoc ISwapModule
    84	    function customMultiplier(address tokenIn, address tokenOut) public view returns (uint256) {
    85	        return _swapModuleStorage().customMultiplier[tokenIn][tokenOut];
    86	    }
    87	
    88	    /// @inheritdoc ISwapModule
    89	    function evaluate(address tokenIn, address tokenOut, uint256 amountIn) public view returns (uint256) {
    90	        tokenIn = tokenIn == TransferLibrary.ETH ? weth : tokenIn;
    91	        tokenOut = tokenOut == TransferLibrary.ETH ? weth : tokenOut;
    92	        uint8 decimalsIn = IERC20Metadata(tokenIn).decimals();
    93	        uint8 decimalsOut = IERC20Metadata(tokenOut).decimals();
    94	        IAaveOracle oracle_ = IAaveOracle(oracle());
    95	        uint256 tokenInPrice = oracle_.getAssetPrice(tokenIn);
    96	        uint256 tokenOutPrice = oracle_.getAssetPrice(tokenOut);
    97	        if (decimalsIn == decimalsOut) {
    98	            return Math.mulDiv(amountIn, tokenInPrice, tokenOutPrice);
    99	        } else if (decimalsIn < decimalsOut) {
   100	            return Math.mulDiv(amountIn, tokenInPrice * 10 ** (decimalsOut - decimalsIn), tokenOutPrice);
   101	        } else {
   102	            return Math.mulDiv(amountIn, tokenInPrice, tokenOutPrice * 10 ** (decimalsIn - decimalsOut));
   103	        }
   104	    }
   105	
   106	    /// @inheritdoc ISwapModule
   107	    function checkParams(Params calldata params) public view {
   108	        if (!hasRole(TOKEN_IN_ROLE, params.tokenIn)) {
   109	            revert Forbidden("tokenIn");
   110	        }
   111	        if (!hasRole(TOKEN_OUT_ROLE, params.tokenOut)) {
   112	            revert Forbidden("tokenOut");
   113	        }
   114	        if (TransferLibrary.balanceOf(params.tokenIn, address(this)) < params.amountIn || params.amountIn == 0) {
   115	            revert Forbidden("amountIn");
   116	        }
   117	        SwapModuleStorage storage $ = _swapModuleStorage();
   118	        uint256 multiplier = $.customMultiplier[params.tokenIn][params.tokenOut];
   119	        if (multiplier == 0) {
   120	            multiplier = $.defaultMultiplier;
   121	        }
   122	        uint256 oracleMinAmount =
   123	            Math.mulDiv(evaluate(params.tokenIn, params.tokenOut, params.amountIn), multiplier, BASE_MULTIPLIER);
   124	
   125	        if (params.minAmountOut < oracleMinAmount) {
   126	            revert Forbidden("minAmountOut < oracleMinAmount");
   127	        }
   128	        if (params.deadline < block.timestamp) {
   129	            revert Forbidden("deadline");
   130	        }
   131	        if (params.tokenIn == params.tokenOut) {
   132	            revert Forbidden("tokenIn == tokenOut");
   133	        }
   134	    }
   135	
   136	    /// @inheritdoc ISwapModule
   137	    function checkCowswapOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
   138	        public
   139	        view
   140	    {
   141	        if (params.tokenIn != address(order.sellToken) || params.tokenIn == TransferLibrary.ETH) {
   142	            revert Forbidden("sellToken");
   143	        }
   144	        if (params.tokenOut != address(order.buyToken) || params.tokenOut == TransferLibrary.ETH) {
   145	            revert Forbidden("buyToken");
   146	        }
   147	        if (address(this) != order.receiver) {
   148	            revert Forbidden("receiver");
   149	        }
   150	        if (params.amountIn != order.sellAmount) {
   151	            revert Forbidden("amountIn != sellAmount");
   152	        }
   153	        if (params.minAmountOut != order.buyAmount) {
   154	            revert Forbidden("minAmountOut != buyAmount");
   155	        }
   156	        if (params.deadline != order.validTo) {
   157	            revert Forbidden("deadline != validTo");
   158	        }
   159	        if (order.kind != GPv2Order.KIND_SELL) {
   160	            revert Forbidden("kind != KIND_SELL");
   161	        }
   162	        if (order.sellTokenBalance != GPv2Order.BALANCE_ERC20) {
   163	            revert Forbidden("sellTokenBalance != BALANCE_ERC20");
   164	        }
   165	        if (order.buyTokenBalance != GPv2Order.BALANCE_ERC20) {
   166	            revert Forbidden("buyTokenBalance != BALANCE_ERC20");
   167	        }
   168	
   169	        bytes memory calculatedOrderUid = new bytes(56);
   170	        GPv2Order.packOrderUidParams(
   171	            calculatedOrderUid,
   172	            GPv2Order.hash(order, GPv2Settlement(payable(cowswapSettlement)).domainSeparator()),
   173	            address(this),
   174	            order.validTo
   175	        );
   176	        if (keccak256(orderUid) != keccak256(calculatedOrderUid)) {
   177	            revert Forbidden("orderUid != calculatedOrderUid");
   178	        }
   179	    }
   180	
   181	    // Mutable functions
   182	
   183	    /// @inheritdoc IFactoryEntity
   184	    function initialize(bytes calldata data) external initializer {
   185	        (
   186	            address admin,
   187	            address subvault_,
   188	            address oracle_,
   189	            uint256 defaultMultiplier_,
   190	            address[] memory holders,
   191	            bytes32[] memory roles
   192	        ) = abi.decode(data, (address, address, address, uint256, address[], bytes32[]));
   193	        if (admin == address(0) || subvault_ == address(0) || oracle_ == address(0)) {
   194	            revert ZeroValue();
   195	        }
   196	        checkMultiplier(defaultMultiplier_);
   197	        SwapModuleStorage storage $ = _swapModuleStorage();
   198	
   199	        _grantRole(DEFAULT_ADMIN_ROLE, admin);
   200	        $.subvault = subvault_;
   201	        $.oracle = oracle_;
   202	        $.defaultMultiplier = defaultMultiplier_;
   203	        for (uint256 i = 0; i < holders.length; i++) {
   204	            if (holders[i] == address(0) || roles[i] == bytes32(0)) {
   205	                revert ZeroValue();
   206	            }
   207	            _grantRole(roles[i], holders[i]);
   208	        }
   209	        emit Initialized(data);
   210	    }
   211	
   212	    /// @inheritdoc ISwapModule
   213	    function setOracle(address oracle_) external onlyRole(DEFAULT_ADMIN_ROLE) {
   214	        if (oracle_ == address(0)) {
   215	            revert ZeroValue();
   216	        }
   217	        _swapModuleStorage().oracle = oracle_;
   218	        emit OracleSet(oracle_);
   219	    }
   220	
   221	    /// @inheritdoc ISwapModule
   222	    function setDefaultMultiplier(uint256 multiplier) external onlyRole(SET_SLIPPAGE_ROLE) {
   223	        checkMultiplier(multiplier);
   224	        _swapModuleStorage().defaultMultiplier = multiplier;
   225	        emit DefaultMultiplierSet(multiplier);
   226	    }
   227	
   228	    /// @inheritdoc ISwapModule
   229	    function setCustomMultiplier(address tokenIn, address tokenOut, uint256 multiplier)
   230	        external
   231	        onlyRole(SET_SLIPPAGE_ROLE)
   232	    {
   233	        checkMultiplier(multiplier);
   234	        _swapModuleStorage().customMultiplier[tokenIn][tokenOut] = multiplier;
   235	        emit CustomMultiplierSet(tokenIn, tokenOut, multiplier);
   236	    }
   237	
   238	    /// @inheritdoc ISwapModule
   239	    function pushAssets(address asset, uint256 value) external payable onlySubvault {
   240	        TransferLibrary.receiveAssets(asset, _msgSender(), value);
   241	        emit AssetsPushed(asset, value);
   242	    }
   243	
   244	    /// @inheritdoc ISwapModule
   245	    function pullAssets(address asset, uint256 value) external onlySubvault {
   246	        TransferLibrary.sendAssets(asset, _msgSender(), value);
   247	        emit AssetsPulled(asset, value);
   248	    }
   249	
   250	    /// @inheritdoc ISwapModule
   251	    function swap(Params calldata params, address router, bytes calldata data)
   252	        external
   253	        nonReentrant
   254	        onlyRole(CALLER_ROLE)
   255	        returns (bytes memory response)
   256	    {
   257	        checkParams(params);
   258	        if (!hasRole(ROUTER_ROLE, router)) {
   259	            revert Forbidden("router");
   260	        }

exec
/bin/bash -lc "sed -n '1,220p' lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    struct ReentrancyGuardStorage {
        uint256 _status;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
        assembly {
            $.slot := ReentrancyGuardStorageLocation
        }
    }

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if ($._status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        $._status = ENTERED;
    }

    function _nonReentrantAfter() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        return $._status == ENTERED;
    }
}

exec
/bin/bash -lc "sed -n '1,220p' lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;


    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
    struct AccessControlStorage {
        mapping(bytes32 role => RoleData) _roles;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;

    function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
        assembly {
            $.slot := AccessControlStorageLocation
        }
    }

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    function __AccessControl_init() internal onlyInitializing {
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        AccessControlStorage storage $ = _getAccessControlStorage();
        bytes32 previousAdminRole = getRoleAdmin(role);
        $._roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        if (!hasRole(role, account)) {
            $._roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *

exec
/bin/bash -lc "sed -n '1,260p' lib/contracts/src/contracts/libraries/GPv2Order.sol" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity >=0.7.6 <0.9.0;

import "../interfaces/IERC20.sol";

/// @title Gnosis Protocol v2 Order Library
/// @author Gnosis Developers
library GPv2Order {
    /// @dev The complete data for a Gnosis Protocol order. This struct contains
    /// all order parameters that are signed for submitting to GP.
    struct Data {
        IERC20 sellToken;
        IERC20 buyToken;
        address receiver;
        uint256 sellAmount;
        uint256 buyAmount;
        uint32 validTo;
        bytes32 appData;
        uint256 feeAmount;
        bytes32 kind;
        bool partiallyFillable;
        bytes32 sellTokenBalance;
        bytes32 buyTokenBalance;
    }

    /// @dev The order EIP-712 type hash for the [`GPv2Order.Data`] struct.
    ///
    /// This value is pre-computed from the following expression:
    /// ```
    /// keccak256(
    ///     "Order(" +
    ///         "address sellToken," +
    ///         "address buyToken," +
    ///         "address receiver," +
    ///         "uint256 sellAmount," +
    ///         "uint256 buyAmount," +
    ///         "uint32 validTo," +
    ///         "bytes32 appData," +
    ///         "uint256 feeAmount," +
    ///         "string kind," +
    ///         "bool partiallyFillable," +
    ///         "string sellTokenBalance," +
    ///         "string buyTokenBalance" +
    ///     ")"
    /// )
    /// ```
    bytes32 internal constant TYPE_HASH =
        hex"d5a25ba2e97094ad7d83dc28a6572da797d6b3e7fc6663bd93efb789fc17e489";

    /// @dev The marker value for a sell order for computing the order struct
    /// hash. This allows the EIP-712 compatible wallets to display a
    /// descriptive string for the order kind (instead of 0 or 1).
    ///
    /// This value is pre-computed from the following expression:
    /// ```
    /// keccak256("sell")
    /// ```
    bytes32 internal constant KIND_SELL =
        hex"f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee346775";

    /// @dev The OrderKind marker value for a buy order for computing the order
    /// struct hash.
    ///
    /// This value is pre-computed from the following expression:
    /// ```
    /// keccak256("buy")
    /// ```
    bytes32 internal constant KIND_BUY =
        hex"6ed88e868af0a1983e3886d5f3e95a2fafbd6c3450bc229e27342283dc429ccc";

    /// @dev The TokenBalance marker value for using direct ERC20 balances for
    /// computing the order struct hash.
    ///
    /// This value is pre-computed from the following expression:
    /// ```
    /// keccak256("erc20")
    /// ```
    bytes32 internal constant BALANCE_ERC20 =
        hex"5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9";

    /// @dev The TokenBalance marker value for using Balancer Vault external
    /// balances (in order to re-use Vault ERC20 approvals) for computing the
    /// order struct hash.
    ///
    /// This value is pre-computed from the following expression:
    /// ```
    /// keccak256("external")
    /// ```
    bytes32 internal constant BALANCE_EXTERNAL =
        hex"abee3b73373acd583a130924aad6dc38cfdc44ba0555ba94ce2ff63980ea0632";

    /// @dev The TokenBalance marker value for using Balancer Vault internal
    /// balances for computing the order struct hash.
    ///
    /// This value is pre-computed from the following expression:
    /// ```
    /// keccak256("internal")
    /// ```
    bytes32 internal constant BALANCE_INTERNAL =
        hex"4ac99ace14ee0a5ef932dc609df0943ab7ac16b7583634612f8dc35a4289a6ce";

    /// @dev Marker address used to indicate that the receiver of the trade
    /// proceeds should the owner of the order.
    ///
    /// This is chosen to be `address(0)` for gas efficiency as it is expected
    /// to be the most common case.
    address internal constant RECEIVER_SAME_AS_OWNER = address(0);

    /// @dev The byte length of an order unique identifier.
    uint256 internal constant UID_LENGTH = 56;

    /// @dev Returns the actual receiver for an order. This function checks
    /// whether or not the [`receiver`] field uses the marker value to indicate
    /// it is the same as the order owner.
    ///
    /// @return receiver The actual receiver of trade proceeds.
    function actualReceiver(
        Data memory order,
        address owner
    ) internal pure returns (address receiver) {
        if (order.receiver == RECEIVER_SAME_AS_OWNER) {
            receiver = owner;
        } else {
            receiver = order.receiver;
        }
    }

    /// @dev Return the EIP-712 signing hash for the specified order.
    ///
    /// @param order The order to compute the EIP-712 signing hash for.
    /// @param domainSeparator The EIP-712 domain separator to use.
    /// @return orderDigest The 32 byte EIP-712 struct hash.
    function hash(
        Data memory order,
        bytes32 domainSeparator
    ) internal pure returns (bytes32 orderDigest) {
        bytes32 structHash;

        // NOTE: Compute the EIP-712 order struct hash in place. As suggested
        // in the EIP proposal, noting that the order struct has 12 fields, and
        // prefixing the type hash `(1 + 12) * 32 = 416` bytes to hash.
        // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#rationale-for-encodedata>
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let dataStart := sub(order, 32)
            let temp := mload(dataStart)
            mstore(dataStart, TYPE_HASH)
            structHash := keccak256(dataStart, 416)
            mstore(dataStart, temp)
        }

        // NOTE: Now that we have the struct hash, compute the EIP-712 signing
        // hash using scratch memory past the free memory pointer. The signing
        // hash is computed from `"\x19\x01" || domainSeparator || structHash`.
        // <https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory>
        // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#specification>
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let freeMemoryPointer := mload(0x40)
            mstore(freeMemoryPointer, "\x19\x01")
            mstore(add(freeMemoryPointer, 2), domainSeparator)
            mstore(add(freeMemoryPointer, 34), structHash)
            orderDigest := keccak256(freeMemoryPointer, 66)
        }
    }

    /// @dev Packs order UID parameters into the specified memory location. The
    /// result is equivalent to `abi.encodePacked(...)` with the difference that
    /// it allows re-using the memory for packing the order UID.
    ///
    /// This function reverts if the order UID buffer is not the correct size.
    ///
    /// @param orderUid The buffer pack the order UID parameters into.
    /// @param orderDigest The EIP-712 struct digest derived from the order
    /// parameters.
    /// @param owner The address of the user who owns this order.
    /// @param validTo The epoch time at which the order will stop being valid.
    function packOrderUidParams(
        bytes memory orderUid,
        bytes32 orderDigest,
        address owner,
        uint32 validTo
    ) internal pure {
        require(orderUid.length == UID_LENGTH, "GPv2: uid buffer overflow");

        // NOTE: Write the order UID to the allocated memory buffer. The order
        // parameters are written to memory in **reverse order** as memory
        // operations write 32-bytes at a time and we want to use a packed
        // encoding. This means, for example, that after writing the value of
        // `owner` to bytes `20:52`, writing the `orderDigest` to bytes `0:32`
        // will **overwrite** bytes `20:32`. This is desirable as addresses are
        // only 20 bytes and `20:32` should be `0`s:
        //
        //        |           1111111111222222222233333333334444444444555555
        //   byte | 01234567890123456789012345678901234567890123456789012345
        // -------+---------------------------------------------------------
        //  field | [.........orderDigest..........][......owner.......][vT]
        // -------+---------------------------------------------------------
        // mstore |                         [000000000000000000000000000.vT]
        //        |                     [00000000000.......owner.......]
        //        | [.........orderDigest..........]
        //
        // Additionally, since Solidity `bytes memory` are length prefixed,
        // 32 needs to be added to all the offsets.
        //
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(add(orderUid, 56), validTo)
            mstore(add(orderUid, 52), owner)
            mstore(add(orderUid, 32), orderDigest)
        }
    }

    /// @dev Extracts specific order information from the standardized unique
    /// order id of the protocol.
    ///
    /// @param orderUid The unique identifier used to represent an order in
    /// the protocol. This uid is the packed concatenation of the order digest,
    /// the validTo order parameter and the address of the user who created the
    /// order. It is used by the user to interface with the contract directly,
    /// and not by calls that are triggered by the solvers.
    /// @return orderDigest The EIP-712 signing digest derived from the order
    /// parameters.
    /// @return owner The address of the user who owns this order.
    /// @return validTo The epoch time at which the order will stop being valid.
    function extractOrderUidParams(
        bytes calldata orderUid
    )
        internal
        pure
        returns (bytes32 orderDigest, address owner, uint32 validTo)
    {
        require(orderUid.length == UID_LENGTH, "GPv2: invalid uid");

        // Use assembly to efficiently decode packed calldata.
        // solhint-disable-next-line no-inline-assembly
        assembly {
            orderDigest := calldataload(orderUid.offset)
            owner := shr(96, calldataload(add(orderUid.offset, 32)))
            validTo := shr(224, calldataload(add(orderUid.offset, 52)))
        }
    }
}

exec
/bin/bash -lc 'rg -n "struct Data|feeAmount|partiallyFillable" lib/contracts/src/contracts/libraries/GPv2Order.sol' in /tmp/codex-ky1rccd0
 succeeded in 0ms:
11:    struct Data {
19:        uint256 feeAmount;
21:        bool partiallyFillable;
39:    ///         "uint256 feeAmount," +
41:    ///         "bool partiallyFillable," +

exec
/bin/bash -lc "rg -n \"feeAmount|sellAmount \\+|partiallyFillable\" lib/contracts/src/contracts -g '"'!**/test/**'"'" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
lib/contracts/src/contracts/GPv2Settlement.sol:56:        uint256 feeAmount,
lib/contracts/src/contracts/GPv2Settlement.sol:192:        feeTransfer.amount = order.feeAmount;
lib/contracts/src/contracts/GPv2Settlement.sol:238:            order.feeAmount,
lib/contracts/src/contracts/GPv2Settlement.sol:379:            if (order.partiallyFillable) {
lib/contracts/src/contracts/GPv2Settlement.sol:381:                executedFeeAmount = order.feeAmount.mul(executedSellAmount).div(
lib/contracts/src/contracts/GPv2Settlement.sol:386:                executedFeeAmount = order.feeAmount;
lib/contracts/src/contracts/GPv2Settlement.sol:401:            if (order.partiallyFillable) {
lib/contracts/src/contracts/GPv2Settlement.sol:403:                executedFeeAmount = order.feeAmount.mul(executedBuyAmount).div(
lib/contracts/src/contracts/GPv2Settlement.sol:408:                executedFeeAmount = order.feeAmount;
lib/contracts/src/contracts/libraries/GPv2Order.sol:19:        uint256 feeAmount;
lib/contracts/src/contracts/libraries/GPv2Order.sol:21:        bool partiallyFillable;
lib/contracts/src/contracts/libraries/GPv2Order.sol:39:    ///         "uint256 feeAmount," +
lib/contracts/src/contracts/libraries/GPv2Order.sol:41:    ///         "bool partiallyFillable," +
lib/contracts/src/contracts/libraries/GPv2Trade.sol:24:        uint256 feeAmount;
lib/contracts/src/contracts/libraries/GPv2Trade.sol:48:        order.feeAmount = trade.feeAmount;
lib/contracts/src/contracts/libraries/GPv2Trade.sol:51:            order.partiallyFillable,
lib/contracts/src/contracts/libraries/GPv2Trade.sol:102:            bool partiallyFillable,
lib/contracts/src/contracts/libraries/GPv2Trade.sol:113:        partiallyFillable = flags & 0x02 != 0;

exec
/bin/bash -lc 'rg -n "filledAmounts|executedAmount|feeAmount" lib/contracts/src/contracts/GPv2Settlement.sol' in /tmp/codex-ky1rccd0
 succeeded in 0ms:
56:        uint256 feeAmount,
151:    /// order will always be fully executed, so the trade's `executedAmount`
175:        uint256 limitAmount = trade.executedAmount;
192:        feeTransfer.amount = order.feeAmount;
238:            order.feeAmount,
314:                trade.executedAmount,
331:    /// @param executedAmount The portion of the order to execute. This will be
341:        uint256 executedAmount,
380:                executedSellAmount = executedAmount;
381:                executedFeeAmount = order.feeAmount.mul(executedSellAmount).div(
386:                executedFeeAmount = order.feeAmount;
402:                executedBuyAmount = executedAmount;
403:                executedFeeAmount = order.feeAmount.mul(executedBuyAmount).div(
408:                executedFeeAmount = order.feeAmount;

exec
/bin/bash -lc "sed -n '330,420p' lib/contracts/src/contracts/GPv2Settlement.sol" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
    /// @param buyPrice The price of the order's buy token.
    /// @param executedAmount The portion of the order to execute. This will be
    /// ignored for fill-or-kill orders.
    /// @param inTransfer Memory location for computed executed sell amount
    /// transfer.
    /// @param outTransfer Memory location for computed executed buy amount
    /// transfer.
    function computeTradeExecution(
        RecoveredOrder memory recoveredOrder,
        uint256 sellPrice,
        uint256 buyPrice,
        uint256 executedAmount,
        GPv2Transfer.Data memory inTransfer,
        GPv2Transfer.Data memory outTransfer
    ) internal {
        GPv2Order.Data memory order = recoveredOrder.data;
        bytes memory orderUid = recoveredOrder.uid;

        // solhint-disable-next-line not-rely-on-time
        require(order.validTo >= block.timestamp, "GPv2: order expired");

        // NOTE: The following computation is derived from the equation:
        // ```
        // amount_x * price_x = amount_y * price_y
        // ```
        // Intuitively, if a chocolate bar is 0,50€ and a beer is 4€, 1 beer
        // is roughly worth 8 chocolate bars (`1 * 4 = 8 * 0.5`). From this
        // equation, we can derive:
        // - The limit price for selling `x` and buying `y` is respected iff
        // ```
        // limit_x * price_x >= limit_y * price_y
        // ```
        // - The executed amount of token `y` given some amount of `x` and
        //   clearing prices is:
        // ```
        // amount_y = amount_x * price_x / price_y
        // ```

        require(
            order.sellAmount.mul(sellPrice) >= order.buyAmount.mul(buyPrice),
            "GPv2: limit price not respected"
        );

        uint256 executedSellAmount;
        uint256 executedBuyAmount;
        uint256 executedFeeAmount;
        uint256 currentFilledAmount;

        if (order.kind == GPv2Order.KIND_SELL) {
            if (order.partiallyFillable) {
                executedSellAmount = executedAmount;
                executedFeeAmount = order.feeAmount.mul(executedSellAmount).div(
                    order.sellAmount
                );
            } else {
                executedSellAmount = order.sellAmount;
                executedFeeAmount = order.feeAmount;
            }

            executedBuyAmount = executedSellAmount.mul(sellPrice).ceilDiv(
                buyPrice
            );

            currentFilledAmount = filledAmount[orderUid].add(
                executedSellAmount
            );
            require(
                currentFilledAmount <= order.sellAmount,
                "GPv2: order filled"
            );
        } else {
            if (order.partiallyFillable) {
                executedBuyAmount = executedAmount;
                executedFeeAmount = order.feeAmount.mul(executedBuyAmount).div(
                    order.buyAmount
                );
            } else {
                executedBuyAmount = order.buyAmount;
                executedFeeAmount = order.feeAmount;
            }

            executedSellAmount = executedBuyAmount.mul(buyPrice).div(sellPrice);

            currentFilledAmount = filledAmount[orderUid].add(executedBuyAmount);
            require(
                currentFilledAmount <= order.buyAmount,
                "GPv2: order filled"
            );
        }

        executedSellAmount = executedSellAmount.add(executedFeeAmount);

exec
/bin/bash -lc "sed -n '160,205p' lib/contracts/src/contracts/GPv2Settlement.sol" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
        recoverOrderFromTrade(recoveredOrder, tokens, trade);

        IVault.SwapKind kind = order.kind == GPv2Order.KIND_SELL
            ? IVault.SwapKind.GIVEN_IN
            : IVault.SwapKind.GIVEN_OUT;

        IVault.FundManagement memory funds;
        funds.sender = recoveredOrder.owner;
        funds.fromInternalBalance =
            order.sellTokenBalance == GPv2Order.BALANCE_INTERNAL;
        funds.recipient = payable(recoveredOrder.receiver);
        funds.toInternalBalance =
            order.buyTokenBalance == GPv2Order.BALANCE_INTERNAL;

        int256[] memory limits = new int256[](tokens.length);
        uint256 limitAmount = trade.executedAmount;
        // NOTE: Array allocation initializes elements to 0, so we only need to
        // set the limits we care about. This ensures that the swap will respect
        // the order's limit price.
        if (order.kind == GPv2Order.KIND_SELL) {
            require(limitAmount >= order.buyAmount, "GPv2: limit too low");
            limits[trade.sellTokenIndex] = order.sellAmount.toInt256();
            limits[trade.buyTokenIndex] = -limitAmount.toInt256();
        } else {
            require(limitAmount <= order.sellAmount, "GPv2: limit too high");
            limits[trade.sellTokenIndex] = limitAmount.toInt256();
            limits[trade.buyTokenIndex] = -order.buyAmount.toInt256();
        }

        GPv2Transfer.Data memory feeTransfer;
        feeTransfer.account = recoveredOrder.owner;
        feeTransfer.token = order.sellToken;
        feeTransfer.amount = order.feeAmount;
        feeTransfer.balance = order.sellTokenBalance;

        int256[] memory tokenDeltas = vaultRelayer.batchSwapWithFee(
            kind,
            swaps,
            tokens,
            funds,
            limits,
            // NOTE: Specify a deadline to ensure that an expire order
            // cannot be used to trade.
            order.validTo,
            feeTransfer
        );

exec
/bin/bash -lc 'rg -n "feeTransfer|transfer fee|fee token|feeAmount" lib/contracts/src/contracts/GPv2VaultRelayer.sol lib/contracts/src/contracts/GPv2Settlement.sol' in /tmp/codex-ky1rccd0
 succeeded in 0ms:
lib/contracts/src/contracts/GPv2Settlement.sol:56:        uint256 feeAmount,
lib/contracts/src/contracts/GPv2Settlement.sol:189:        GPv2Transfer.Data memory feeTransfer;
lib/contracts/src/contracts/GPv2Settlement.sol:190:        feeTransfer.account = recoveredOrder.owner;
lib/contracts/src/contracts/GPv2Settlement.sol:191:        feeTransfer.token = order.sellToken;
lib/contracts/src/contracts/GPv2Settlement.sol:192:        feeTransfer.amount = order.feeAmount;
lib/contracts/src/contracts/GPv2Settlement.sol:193:        feeTransfer.balance = order.sellTokenBalance;
lib/contracts/src/contracts/GPv2Settlement.sol:204:            feeTransfer
lib/contracts/src/contracts/GPv2Settlement.sol:238:            order.feeAmount,
lib/contracts/src/contracts/GPv2Settlement.sol:381:                executedFeeAmount = order.feeAmount.mul(executedSellAmount).div(
lib/contracts/src/contracts/GPv2Settlement.sol:386:                executedFeeAmount = order.feeAmount;
lib/contracts/src/contracts/GPv2Settlement.sol:403:                executedFeeAmount = order.feeAmount.mul(executedBuyAmount).div(
lib/contracts/src/contracts/GPv2Settlement.sol:408:                executedFeeAmount = order.feeAmount;
lib/contracts/src/contracts/GPv2VaultRelayer.sol:64:    /// @param feeTransfer The transfer data for the caller fee.
lib/contracts/src/contracts/GPv2VaultRelayer.sol:73:        GPv2Transfer.Data calldata feeTransfer
lib/contracts/src/contracts/GPv2VaultRelayer.sol:83:        vault.fastTransferFromAccount(feeTransfer, msg.sender);

exec
/bin/bash -lc "sed -n '1,260p' lib/contracts/src/contracts/GPv2VaultRelayer.sol" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity >=0.7.6 <0.9.0;
pragma abicoder v2;

import "./interfaces/IERC20.sol";
import "./interfaces/IVault.sol";
import "./libraries/GPv2Transfer.sol";

/// @title Gnosis Protocol v2 Vault Relayer Contract
/// @author Gnosis Developers
contract GPv2VaultRelayer {
    using GPv2Transfer for IVault;

    /// @dev The creator of the contract which has special permissions. This
    /// value is set at creation time and cannot change.
    address private immutable creator;

    /// @dev The vault this relayer is for.
    IVault private immutable vault;

    constructor(IVault vault_) {
        creator = msg.sender;
        vault = vault_;
    }

    /// @dev Modifier that ensures that a function can only be called by the
    /// creator of this contract.
    modifier onlyCreator() {
        require(msg.sender == creator, "GPv2: not creator");
        _;
    }

    /// @dev Transfers all sell amounts for the executed trades from their
    /// owners to the caller.
    ///
    /// This function reverts if:
    /// - The caller is not the creator of the vault relayer
    /// - Any ERC20 transfer fails
    ///
    /// @param transfers The transfers to execute.
    function transferFromAccounts(
        GPv2Transfer.Data[] calldata transfers
    ) external onlyCreator {
        vault.transferFromAccounts(transfers, msg.sender);
    }

    /// @dev Performs a Balancer batched swap on behalf of a user and sends a
    /// fee to the caller.
    ///
    /// This function reverts if:
    /// - The caller is not the creator of the vault relayer
    /// - The swap fails
    /// - The fee transfer fails
    ///
    /// @param kind The Balancer swap kind, this can either be `GIVEN_IN` for
    /// sell orders or `GIVEN_OUT` for buy orders.
    /// @param swaps The swaps to perform.
    /// @param tokens The tokens for the swaps. Swaps encode to and from tokens
    /// as indices into this array.
    /// @param funds The fund management settings, specifying the user the swap
    /// is being performed for as well as the recipient of the proceeds.
    /// @param limits Swap limits for encoding limit prices.
    /// @param deadline The deadline for the swap.
    /// @param feeTransfer The transfer data for the caller fee.
    /// @return tokenDeltas The executed swap amounts.
    function batchSwapWithFee(
        IVault.SwapKind kind,
        IVault.BatchSwapStep[] calldata swaps,
        IERC20[] memory tokens,
        IVault.FundManagement memory funds,
        int256[] memory limits,
        uint256 deadline,
        GPv2Transfer.Data calldata feeTransfer
    ) external onlyCreator returns (int256[] memory tokenDeltas) {
        tokenDeltas = vault.batchSwap(
            kind,
            swaps,
            tokens,
            funds,
            limits,
            deadline
        );
        vault.fastTransferFromAccount(feeTransfer, msg.sender);
    }
}

exec
/bin/bash -lc "nl -ba src/permissions/MellowACL.sol | sed -n '1,220p'" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
     1	// SPDX-License-Identifier: BUSL-1.1
     2	pragma solidity 0.8.25;
     3	
     4	import "../interfaces/permissions/IMellowACL.sol";
     5	
     6	import "../libraries/SlotLibrary.sol";
     7	
     8	abstract contract MellowACL is IMellowACL, AccessControlEnumerableUpgradeable {
     9	    using EnumerableSet for EnumerableSet.Bytes32Set;
    10	
    11	    bytes32 private immutable _mellowACLStorageSlot;
    12	
    13	    constructor(string memory name_, uint256 version_) {
    14	        _mellowACLStorageSlot = SlotLibrary.getSlot("MellowACL", name_, version_);
    15	        _disableInitializers();
    16	    }
    17	
    18	    // View functions
    19	
    20	    /// @inheritdoc IMellowACL
    21	    function supportedRoles() external view returns (uint256) {
    22	        return _mellowACLStorage().supportedRoles.length();
    23	    }
    24	
    25	    /// @inheritdoc IMellowACL
    26	    function supportedRoleAt(uint256 index) external view returns (bytes32) {
    27	        return _mellowACLStorage().supportedRoles.at(index);
    28	    }
    29	
    30	    /// @inheritdoc IMellowACL
    31	    function hasSupportedRole(bytes32 role) external view returns (bool) {
    32	        return _mellowACLStorage().supportedRoles.contains(role);
    33	    }
    34	
    35	    // Internal functions
    36	
    37	    function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
    38	        if (super._grantRole(role, account)) {
    39	            if (_mellowACLStorage().supportedRoles.add(role)) {
    40	                emit RoleAdded(role);
    41	            }
    42	            return true;
    43	        }
    44	        return false;
    45	    }
    46	
    47	    function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
    48	        if (super._revokeRole(role, account)) {
    49	            if (getRoleMemberCount(role) == 0) {
    50	                _mellowACLStorage().supportedRoles.remove(role);
    51	                emit RoleRemoved(role);
    52	            }
    53	            return true;
    54	        }
    55	        return false;
    56	    }
    57	
    58	    function _mellowACLStorage() private view returns (MellowACLStorage storage $) {
    59	        bytes32 slot = _mellowACLStorageSlot;
    60	        assembly {
    61	            $.slot := slot
    62	        }
    63	    }
    64	}

exec
/bin/bash -lc "nl -ba src/libraries/SlotLibrary.sol | sed -n '1,120p'" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
     1	// SPDX-License-Identifier: BUSL-1.1
     2	pragma solidity 0.8.25;
     3	
     4	/// @title SlotLibrary
     5	/// @notice Library for computing deterministic and collision-resistant storage slots
     6	/// @dev Used to generate unique storage slots for upgradeable modules using string identifiers
     7	library SlotLibrary {
     8	    /// @notice Computes a unique storage slot based on the module's identifiers
     9	    /// @param contractName Logical contract/module name (e.g., "ShareModule")
    10	    /// @param name Human-readable instance name (e.g., "Mellow")
    11	    /// @param version Version number for the module configuration
    12	    /// @return A bytes32 value representing the derived storage slot
    13	    function getSlot(string memory contractName, string memory name, uint256 version) internal pure returns (bytes32) {
    14	        return keccak256(
    15	            abi.encode(
    16	                uint256(keccak256(abi.encodePacked("mellow.flexible-vaults.storage.", contractName, name, version))) - 1
    17	            )
    18	        ) & ~bytes32(uint256(0xff));
    19	    }
    20	}

exec
/bin/bash -lc "nl -ba src/interfaces/utils/ISwapModule.sol | sed -n '1,220p'" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
     1	// SPDX-License-Identifier: BUSL-1.1
     2	pragma solidity 0.8.25;
     3	
     4	import "../external/aave/IAaveOracle.sol";
     5	
     6	import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
     7	import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
     8	import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
     9	import "@openzeppelin/contracts/utils/math/Math.sol";
    10	
    11	import {GPv2Settlement} from "@cowswap/contracts/GPv2Settlement.sol";
    12	import {GPv2Order} from "@cowswap/contracts/libraries/GPv2Order.sol";
    13	
    14	import "../factories/IFactoryEntity.sol";
    15	
    16	interface ISwapModule is IFactoryEntity {
    17	    // Errors
    18	
    19	    error Forbidden(string reason);
    20	    error ZeroValue();
    21	
    22	    // Structs
    23	
    24	    struct SwapModuleStorage {
    25	        address subvault;
    26	        address oracle;
    27	        uint256 defaultMultiplier;
    28	        mapping(address => mapping(address => uint256)) customMultiplier;
    29	    }
    30	
    31	    struct Params {
    32	        address tokenIn;
    33	        address tokenOut;
    34	        uint256 amountIn;
    35	        uint256 minAmountOut;
    36	        uint256 deadline;
    37	    }
    38	
    39	    // View functions
    40	
    41	    function MIN_MULTIPLIER() external view returns (uint256);
    42	    function MAX_MULTIPLIER() external view returns (uint256);
    43	    function BASE_MULTIPLIER() external view returns (uint256);
    44	
    45	    function TOKEN_IN_ROLE() external view returns (bytes32);
    46	    function TOKEN_OUT_ROLE() external view returns (bytes32);
    47	    function ROUTER_ROLE() external view returns (bytes32);
    48	    function CALLER_ROLE() external view returns (bytes32);
    49	    function SET_SLIPPAGE_ROLE() external view returns (bytes32);
    50	
    51	    function cowswapSettlement() external view returns (address);
    52	    function cowswapVaultRelayer() external view returns (address);
    53	    function weth() external view returns (address);
    54	
    55	    function subvault() external view returns (address);
    56	
    57	    function oracle() external view returns (address);
    58	
    59	    function checkMultiplier(uint256 multiplier) external pure;
    60	
    61	    function defaultMultiplier() external view returns (uint256);
    62	
    63	    function customMultiplier(address tokenIn, address tokenOut) external view returns (uint256);
    64	
    65	    function evaluate(address tokenIn, address tokenOut, uint256 amountIn) external view returns (uint256);
    66	
    67	    function checkParams(Params calldata params) external view;
    68	
    69	    function checkCowswapOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
    70	        external
    71	        view;
    72	
    73	    // Mutable functions
    74	
    75	    function setOracle(address oracle_) external;
    76	
    77	    function setDefaultMultiplier(uint256 multiplier) external;
    78	
    79	    function setCustomMultiplier(address tokenIn, address tokenOut, uint256 multiplier) external;
    80	
    81	    function pushAssets(address asset, uint256 value) external payable;
    82	
    83	    function pullAssets(address asset, uint256 value) external;
    84	
    85	    function swap(Params calldata params, address router, bytes calldata data)
    86	        external
    87	        returns (bytes memory response);
    88	
    89	    function setCowswapApproval(address asset, uint256 amount) external;
    90	
    91	    function createLimitOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
    92	        external;
    93	
    94	    function invalidateOrder(bytes calldata orderUid) external;
    95	
    96	    // Events
    97	
    98	    event AssetsPulled(address indexed asset, uint256 value);
    99	
   100	    event AssetsPushed(address indexed asset, uint256 value);
   101	
   102	    event DefaultMultiplierSet(uint256 indexed multiplier);
   103	
   104	    event CustomMultiplierSet(address indexed tokenIn, address indexed tokenOut, uint256 indexed multiplier);
   105	
   106	    event CowswapApprovalSet(address indexed asset, uint256 amount);
   107	
   108	    event LimitOrderCreated(Params params, bytes orderUid);
   109	
   110	    event LimitOrderInvalidated(bytes orderUid);
   111	
   112	    event Swap(Params params, address router, uint256 amountOut);
   113	
   114	    event OracleSet(address indexed oracle);
   115	}

exec
/bin/bash -lc "nl -ba src/utils/SwapModule.sol | sed -n '260,340p'" in /tmp/codex-ky1rccd0
 succeeded in 0ms:
   260	        }
   261	        uint256 balanceBefore = TransferLibrary.balanceOf(params.tokenOut, address(this));
   262	        if (params.tokenIn != TransferLibrary.ETH) {
   263	            IERC20(params.tokenIn).forceApprove(router, params.amountIn);
   264	            response = Address.functionCall(router, data);
   265	            IERC20(params.tokenIn).forceApprove(router, 0);
   266	        } else {
   267	            response = Address.functionCallWithValue(router, data, params.amountIn);
   268	        }
   269	        uint256 amountOut = TransferLibrary.balanceOf(params.tokenOut, address(this)) - balanceBefore;
   270	        if (amountOut < params.minAmountOut) {
   271	            revert Forbidden("amountOut < minAmountOut");
   272	        }
   273	        emit Swap(params, router, amountOut);
   274	    }
   275	
   276	    /// @inheritdoc ISwapModule
   277	    function setCowswapApproval(address asset, uint256 amount) external onlyRole(CALLER_ROLE) {
   278	        if (asset == TransferLibrary.ETH || !hasRole(TOKEN_IN_ROLE, asset)) {
   279	            revert Forbidden("asset");
   280	        }
   281	        IERC20(asset).forceApprove(cowswapVaultRelayer, amount);
   282	        emit CowswapApprovalSet(asset, amount);
   283	    }
   284	
   285	    /// @inheritdoc ISwapModule
   286	    function createLimitOrder(Params calldata params, GPv2Order.Data calldata order, bytes calldata orderUid)
   287	        external
   288	        onlyRole(CALLER_ROLE)
   289	    {
   290	        checkParams(params);
   291	        checkCowswapOrder(params, order, orderUid);
   292	        GPv2Settlement(payable(cowswapSettlement)).setPreSignature(orderUid, true);
   293	        emit LimitOrderCreated(params, orderUid);
   294	    }
   295	
   296	    /// @inheritdoc ISwapModule
   297	    function invalidateOrder(bytes calldata orderUid) external onlyRole(CALLER_ROLE) {
   298	        GPv2Settlement(payable(cowswapSettlement)).invalidateOrder(orderUid);
   299	        emit LimitOrderInvalidated(orderUid);
   300	    }
   301	
   302	    receive() external payable {}
   303	
   304	    // Internal functions
   305	
   306	    function _swapModuleStorage() internal view returns (SwapModuleStorage storage $) {
   307	        bytes32 slot = _swapModuleStorageSlot;
   308	        assembly {
   309	            $.slot := slot
   310	        }
   311	    }
   312	}

codex
{"no_findings":false,"summary":"Read `AUDIT_BUNDLE.txt` in full. Found 2 material issues in the implementation: proxy upgrades can strand state because storage slots are version/name-dependent, and CoWSwap orders do not bound `feeAmount`, allowing approved sell tokens to be overspent beyond `params.amountIn`.","findings":[{"severity":"high","category":"upgradeability","title":"Version-dependent storage slots can brick the proxy and strand assets on upgrade","exploit_scenario":"The proxy points at this implementation today. If the proxy admin later upgrades to a new `SwapModule` implementation deployed with a different `name_` or `version_`, the immutables `_swapModuleStorageSlot` and `_mellowACLStorageSlot` change. After the upgrade, `subvault()`, `oracle()`, multipliers, and supported-role bookkeeping are read from empty storage. Because `subvault` has no setter and `initialize` is one-shot, the proxy can no longer satisfy `onlySubvault`, leaving assets held by the proxy permanently stuck.","recommendation":"Do not derive live storage slots from constructor-time version/name inputs for proxy state. Use fixed ERC-7201/ERC-1967-style slots that remain identical across implementation versions, or enforce that every upgrade reuses the exact same slot constants.","evidence":"`SlotLibrary.getSlot` mixes `contractName`, `name`, and `version` into the slot seed. `MellowACL` and `SwapModule` cache those derived slots in immutables during construction and use them for all reads/writes of first-party state, so a new implementation with different constructor args silently points the proxy at a different storage region.","locations":["src/libraries/SlotLibrary.sol:13","src/permissions/MellowACL.sol:11","src/permissions/MellowACL.sol:58","src/utils/SwapModule.sol:37","src/utils/SwapModule.sol:62","src/utils/SwapModule.sol:197","src/utils/SwapModule.sol:306"]},{"severity":"medium","category":"access_control","title":"`createLimitOrder` does not constrain CoWSwap `feeAmount`, so CALLER_ROLE can spend more than `params.amountIn`","exploit_scenario":"A compromised or over-scoped `CALLER_ROLE` can first call `setCowswapApproval(asset, type(uint256).max)`, then create a pre-signed CoWSwap order with `sellAmount == params.amountIn` but an arbitrarily large `feeAmount`. The order passes `checkParams` and `checkCowswapOrder`, yet CoWSwap settlement will pull `sellAmount + feeAmount` from the module. The extra sell token is outside the min-out/slippage checks, so approved balances can be drained far beyond the amount the module intended to trade.","recommendation":"Reject orders with nonzero `feeAmount`, or explicitly include fee in the validated parameters and in the balance/allowance accounting. If partial fills are not needed, also require `order.partiallyFillable == false` and scope approval to the exact live exposure instead of a reusable allowance.","evidence":"`checkCowswapOrder` validates token addresses, `sellAmount`, `buyAmount`, deadline, kind, and balance modes, but never checks `order.feeAmount` (or `order.partiallyFillable`). Separately, `setCowswapApproval` allows arbitrary allowance to `cowswapVaultRelayer`, so settlement can legally transfer more sell token than `params.amountIn`.","locations":["src/utils/SwapModule.sol:137","src/utils/SwapModule.sol:150","src/utils/SwapModule.sol:153","src/utils/SwapModule.sol:277","src/utils/SwapModule.sol:286"]}]}
tokens used
56,429
Metadata
{
    "codex": {
        "ran_at": "2026-04-04T03:51:38.608562+00:00",
        "returncode": 0
    },
    "project": {
        "mode": "source",
        "file_count": 49,
        "import_count": 60,
        "first_party_contracts_count": 8
    },
    "summary": "Read `AUDIT_BUNDLE.txt` in full. Found 2 material issues in the implementation: proxy upgrades can strand state because storage slots are version/name-dependent, and CoWSwap orders do not bound `feeAmount`, allowing approved sell tokens to be overspent beyond `params.amountIn`.",
    "raw_final": {
        "summary": "Read `AUDIT_BUNDLE.txt` in full. Found 2 material issues in the implementation: proxy upgrades can strand state because storage slots are version/name-dependent, and CoWSwap orders do not bound `feeAmount`, allowing approved sell tokens to be overspent beyond `params.amountIn`.",
        "findings": [
            {
                "title": "Version-dependent storage slots can brick the proxy and strand assets on upgrade",
                "category": "upgradeability",
                "evidence": "`SlotLibrary.getSlot` mixes `contractName`, `name`, and `version` into the slot seed. `MellowACL` and `SwapModule` cache those derived slots in immutables during construction and use them for all reads/writes of first-party state, so a new implementation with different constructor args silently points the proxy at a different storage region.",
                "severity": "high",
                "locations": [
                    "src/libraries/SlotLibrary.sol:13",
                    "src/permissions/MellowACL.sol:11",
                    "src/permissions/MellowACL.sol:58",
                    "src/utils/SwapModule.sol:37",
                    "src/utils/SwapModule.sol:62",
                    "src/utils/SwapModule.sol:197",
                    "src/utils/SwapModule.sol:306"
                ],
                "recommendation": "Do not derive live storage slots from constructor-time version/name inputs for proxy state. Use fixed ERC-7201/ERC-1967-style slots that remain identical across implementation versions, or enforce that every upgrade reuses the exact same slot constants.",
                "exploit_scenario": "The proxy points at this implementation today. If the proxy admin later upgrades to a new `SwapModule` implementation deployed with a different `name_` or `version_`, the immutables `_swapModuleStorageSlot` and `_mellowACLStorageSlot` change. After the upgrade, `subvault()`, `oracle()`, multipliers, and supported-role bookkeeping are read from empty storage. Because `subvault` has no setter and `initialize` is one-shot, the proxy can no longer satisfy `onlySubvault`, leaving assets held by the proxy permanently stuck."
            },
            {
                "title": "`createLimitOrder` does not constrain CoWSwap `feeAmount`, so CALLER_ROLE can spend more than `params.amountIn`",
                "category": "access_control",
                "evidence": "`checkCowswapOrder` validates token addresses, `sellAmount`, `buyAmount`, deadline, kind, and balance modes, but never checks `order.feeAmount` (or `order.partiallyFillable`). Separately, `setCowswapApproval` allows arbitrary allowance to `cowswapVaultRelayer`, so settlement can legally transfer more sell token than `params.amountIn`.",
                "severity": "medium",
                "locations": [
                    "src/utils/SwapModule.sol:137",
                    "src/utils/SwapModule.sol:150",
                    "src/utils/SwapModule.sol:153",
                    "src/utils/SwapModule.sol:277",
                    "src/utils/SwapModule.sol:286"
                ],
                "recommendation": "Reject orders with nonzero `feeAmount`, or explicitly include fee in the validated parameters and in the balance/allowance accounting. If partial fills are not needed, also require `order.partiallyFillable == false` and scope approval to the exact live exposure instead of a reusable allowance.",
                "exploit_scenario": "A compromised or over-scoped `CALLER_ROLE` can first call `setCowswapApproval(asset, type(uint256).max)`, then create a pre-signed CoWSwap order with `sellAmount == params.amountIn` but an arbitrarily large `feeAmount`. The order passes `checkParams` and `checkCowswapOrder`, yet CoWSwap settlement will pull `sellAmount + feeAmount` from the module. The extra sell token is outside the min-out/slippage checks, so approved balances can be drained far beyond the amount the module intended to trade."
            }
        ],
        "no_findings": false
    },
    "no_findings": false,
    "proxy_context": {
        "proxy_run_id": "019d5666-721c-732b-98f2-8fef0dd24f32",
        "proxy_address": "0xc95b806ac073df930014ac476d26c8ad918f14e0",
        "implementation_run_id": "019d5666-f542-7208-84bc-d2e3db83e367",
        "implementation_address": "0x00000000d681e85e5783588f87a9573cb97eda01"
    },
    "schema_version": 1
}

Opcode Heuristics

tool.cast_disasm
Delegatecall
2
Selfdestruct
0
CREATE2
0
CALL-family (heavy)
0
EXT*/BALANCE
2
Total opcodes
696
Flags
delegatecall_present
View cast disassembly output
00000000: PUSH1 0x80
00000002: PUSH1 0x40
00000004: MSTORE
00000005: PUSH2 0x000c
00000008: PUSH2 0x000e
0000000b: JUMP
0000000c: JUMPDEST
0000000d: STOP
0000000e: JUMPDEST
0000000f: PUSH32 0x000000000000000000000000b1c296d637866c309443f4dd77fd59146a925daf
00000030: PUSH1 0x01
00000032: PUSH1 0x01
00000034: PUSH1 0xa0
00000036: SHL
00000037: SUB
00000038: AND
00000039: CALLER
0000003a: SUB
0000003b: PUSH2 0x007a
0000003e: JUMPI
0000003f: PUSH0
00000040: CALLDATALOAD
00000041: PUSH1 0x01
00000043: PUSH1 0x01
00000045: PUSH1 0xe0
00000047: SHL
00000048: SUB
00000049: NOT
0000004a: AND
0000004b: PUSH4 0x278f7943
00000050: PUSH1 0xe1
00000052: SHL
00000053: EQ
00000054: PUSH2 0x0070
00000057: JUMPI
00000058: PUSH1 0x40
0000005a: MLOAD
0000005b: PUSH4 0x34ad5dbb
00000060: PUSH1 0xe2
00000062: SHL
00000063: DUP2
00000064: MSTORE
00000065: PUSH1 0x04
00000067: ADD
00000068: PUSH1 0x40
0000006a: MLOAD
0000006b: DUP1
0000006c: SWAP2
0000006d: SUB
0000006e: SWAP1
0000006f: REVERT
00000070: JUMPDEST
00000071: PUSH2 0x0078
00000074: PUSH2 0x0082
00000077: JUMP
00000078: JUMPDEST
00000079: JUMP
0000007a: JUMPDEST
0000007b: PUSH2 0x0078
0000007e: PUSH2 0x00b0
00000081: JUMP
00000082: JUMPDEST
00000083: PUSH0
00000084: DUP1
00000085: PUSH2 0x0091
00000088: CALLDATASIZE
00000089: PUSH1 0x04
0000008b: DUP2
0000008c: DUP5
0000008d: PUSH2 0x0303
00000090: JUMP
00000091: JUMPDEST
00000092: DUP2
00000093: ADD
00000094: SWAP1
00000095: PUSH2 0x009e
00000098: SWAP2
00000099: SWAP1
0000009a: PUSH2 0x033e
0000009d: JUMP
0000009e: JUMPDEST
0000009f: SWAP2
000000a0: POP
000000a1: SWAP2
000000a2: POP
000000a3: PUSH2 0x00ac
000000a6: DUP3
000000a7: DUP3
000000a8: PUSH2 0x00c0
000000ab: JUMP
000000ac: JUMPDEST
000000ad: POP
000000ae: POP
000000af: JUMP
000000b0: JUMPDEST
000000b1: PUSH2 0x0078
000000b4: PUSH2 0x00bb
000000b7: PUSH2 0x011a
000000ba: JUMP
000000bb: JUMPDEST
000000bc: PUSH2 0x0151
000000bf: JUMP
000000c0: JUMPDEST
000000c1: PUSH2 0x00c9
000000c4: DUP3
000000c5: PUSH2 0x016f
000000c8: JUMP
000000c9: JUMPDEST
000000ca: PUSH1 0x40
000000cc: MLOAD
000000cd: PUSH1 0x01
000000cf: PUSH1 0x01
000000d1: PUSH1 0xa0
000000d3: SHL
000000d4: SUB
000000d5: DUP4
000000d6: AND
000000d7: SWAP1
000000d8: PUSH32 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b
000000f9: SWAP1
000000fa: PUSH0
000000fb: SWAP1
000000fc: LOG2
000000fd: DUP1
000000fe: MLOAD
000000ff: ISZERO
00000100: PUSH2 0x0112
00000103: JUMPI
00000104: PUSH2 0x010d
00000107: DUP3
00000108: DUP3
00000109: PUSH2 0x01ea
0000010c: JUMP
0000010d: JUMPDEST
0000010e: POP
0000010f: POP
00000110: POP
00000111: JUMP
00000112: JUMPDEST
00000113: PUSH2 0x00ac
00000116: PUSH2 0x025c
00000119: JUMP
0000011a: JUMPDEST
0000011b: PUSH0
0000011c: PUSH2 0x014c
0000011f: PUSH32 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
00000140: SLOAD
00000141: PUSH1 0x01
00000143: PUSH1 0x01
00000145: PUSH1 0xa0
00000147: SHL
00000148: SUB
00000149: AND
0000014a: SWAP1
0000014b: JUMP
0000014c: JUMPDEST
0000014d: SWAP1
0000014e: POP
0000014f: SWAP1
00000150: JUMP
00000151: JUMPDEST
00000152: CALLDATASIZE
00000153: PUSH0
00000154: DUP1
00000155: CALLDATACOPY
00000156: PUSH0
00000157: DUP1
00000158: CALLDATASIZE
00000159: PUSH0
0000015a: DUP5
0000015b: GAS
0000015c: DELEGATECALL
0000015d: RETURNDATASIZE
0000015e: PUSH0
0000015f: DUP1
00000160: RETURNDATACOPY
00000161: DUP1
00000162: DUP1
00000163: ISZERO
00000164: PUSH2 0x016b
00000167: JUMPI
00000168: RETURNDATASIZE
00000169: PUSH0
0000016a: RETURN
0000016b: JUMPDEST
0000016c: RETURNDATASIZE
0000016d: PUSH0
0000016e: REVERT
0000016f: JUMPDEST
00000170: DUP1
00000171: PUSH1 0x01
00000173: PUSH1 0x01
00000175: PUSH1 0xa0
00000177: SHL
00000178: SUB
00000179: AND
0000017a: EXTCODESIZE
0000017b: PUSH0
0000017c: SUB
0000017d: PUSH2 0x01a9
00000180: JUMPI
00000181: PUSH1 0x40
00000183: MLOAD
00000184: PUSH4 0x4c9c8ce3
00000189: PUSH1 0xe0
0000018b: SHL
0000018c: DUP2
0000018d: MSTORE
0000018e: PUSH1 0x01
00000190: PUSH1 0x01
00000192: PUSH1 0xa0
00000194: SHL
00000195: SUB
00000196: DUP3
00000197: AND
00000198: PUSH1 0x04
0000019a: DUP3
0000019b: ADD
0000019c: MSTORE
0000019d: PUSH1 0x24
0000019f: ADD
000001a0: JUMPDEST
000001a1: PUSH1 0x40
000001a3: MLOAD
000001a4: DUP1
000001a5: SWAP2
000001a6: SUB
000001a7: SWAP1
000001a8: REVERT
000001a9: JUMPDEST
000001aa: PUSH32 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
000001cb: DUP1
000001cc: SLOAD
000001cd: PUSH1 0x01
000001cf: PUSH1 0x01
000001d1: PUSH1 0xa0
000001d3: SHL
000001d4: SUB
000001d5: NOT
000001d6: AND
000001d7: PUSH1 0x01
000001d9: PUSH1 0x01
000001db: PUSH1 0xa0
000001dd: SHL
000001de: SUB
000001df: SWAP3
000001e0: SWAP1
000001e1: SWAP3
000001e2: AND
000001e3: SWAP2
000001e4: SWAP1
000001e5: SWAP2
000001e6: OR
000001e7: SWAP1
000001e8: SSTORE
000001e9: JUMP
000001ea: JUMPDEST
000001eb: PUSH1 0x60
000001ed: PUSH0
000001ee: DUP1
000001ef: DUP5
000001f0: PUSH1 0x01
000001f2: PUSH1 0x01
000001f4: PUSH1 0xa0
000001f6: SHL
000001f7: SUB
000001f8: AND
000001f9: DUP5
000001fa: PUSH1 0x40
000001fc: MLOAD
000001fd: PUSH2 0x0206
00000200: SWAP2
00000201: SWAP1
00000202: PUSH2 0x0407
00000205: JUMP
00000206: JUMPDEST
00000207: PUSH0
00000208: PUSH1 0x40
0000020a: MLOAD
0000020b: DUP1
0000020c: DUP4
0000020d: SUB
0000020e: DUP2
0000020f: DUP6
00000210: GAS
00000211: DELEGATECALL
00000212: SWAP2
00000213: POP
00000214: POP
00000215: RETURNDATASIZE
00000216: DUP1
00000217: PUSH0
00000218: DUP2
00000219: EQ
0000021a: PUSH2 0x023e
0000021d: JUMPI
0000021e: PUSH1 0x40
00000220: MLOAD
00000221: SWAP2
00000222: POP
00000223: PUSH1 0x1f
00000225: NOT
00000226: PUSH1 0x3f
00000228: RETURNDATASIZE
00000229: ADD
0000022a: AND
0000022b: DUP3
0000022c: ADD
0000022d: PUSH1 0x40
0000022f: MSTORE
00000230: RETURNDATASIZE
00000231: DUP3
00000232: MSTORE
00000233: RETURNDATASIZE
00000234: PUSH0
00000235: PUSH1 0x20
00000237: DUP5
00000238: ADD
00000239: RETURNDATACOPY
0000023a: PUSH2 0x0243
0000023d: JUMP
0000023e: JUMPDEST
0000023f: PUSH1 0x60
00000241: SWAP2
00000242: POP
00000243: JUMPDEST
00000244: POP
00000245: SWAP2
00000246: POP
00000247: SWAP2
00000248: POP
00000249: PUSH2 0x0253
0000024c: DUP6
0000024d: DUP4
0000024e: DUP4
0000024f: PUSH2 0x027b
00000252: JUMP
00000253: JUMPDEST
00000254: SWAP6
00000255: SWAP5
00000256: POP
00000257: POP
00000258: POP
00000259: POP
0000025a: POP
0000025b: JUMP
0000025c: JUMPDEST
0000025d: CALLVALUE
0000025e: ISZERO
0000025f: PUSH2 0x0078
00000262: JUMPI
00000263: PUSH1 0x40
00000265: MLOAD
00000266: PUSH4 0xb398979f
0000026b: PUSH1 0xe0
0000026d: SHL
0000026e: DUP2
0000026f: MSTORE
00000270: PUSH1 0x04
00000272: ADD
00000273: PUSH1 0x40
00000275: MLOAD
00000276: DUP1
00000277: SWAP2
00000278: SUB
00000279: SWAP1
0000027a: REVERT
0000027b: JUMPDEST
0000027c: PUSH1 0x60
0000027e: DUP3
0000027f: PUSH2 0x0290
00000282: JUMPI
00000283: PUSH2 0x028b
00000286: DUP3
00000287: PUSH2 0x02da
0000028a: JUMP
0000028b: JUMPDEST
0000028c: PUSH2 0x02d3
0000028f: JUMP
00000290: JUMPDEST
00000291: DUP2
00000292: MLOAD
00000293: ISZERO
00000294: DUP1
00000295: ISZERO
00000296: PUSH2 0x02a7
00000299: JUMPI
0000029a: POP
0000029b: PUSH1 0x01
0000029d: PUSH1 0x01
0000029f: PUSH1 0xa0
000002a1: SHL
000002a2: SUB
000002a3: DUP5
000002a4: AND
000002a5: EXTCODESIZE
000002a6: ISZERO
000002a7: JUMPDEST
000002a8: ISZERO
000002a9: PUSH2 0x02d0
000002ac: JUMPI
000002ad: PUSH1 0x40
000002af: MLOAD
000002b0: PUSH4 0x9996b315
000002b5: PUSH1 0xe0
000002b7: SHL
000002b8: DUP2
000002b9: MSTORE
000002ba: PUSH1 0x01
000002bc: PUSH1 0x01
000002be: PUSH1 0xa0
000002c0: SHL
000002c1: SUB
000002c2: DUP6
000002c3: AND
000002c4: PUSH1 0x04
000002c6: DUP3
000002c7: ADD
000002c8: MSTORE
000002c9: PUSH1 0x24
000002cb: ADD
000002cc: PUSH2 0x01a0
000002cf: JUMP
000002d0: JUMPDEST
000002d1: POP
000002d2: DUP1
000002d3: JUMPDEST
000002d4: SWAP4
000002d5: SWAP3
000002d6: POP
000002d7: POP
000002d8: POP
000002d9: JUMP
000002da: JUMPDEST
000002db: DUP1
000002dc: MLOAD
000002dd: ISZERO
000002de: PUSH2 0x02ea
000002e1: JUMPI
000002e2: DUP1
000002e3: MLOAD
000002e4: DUP1
000002e5: DUP3
000002e6: PUSH1 0x20
000002e8: ADD
000002e9: REVERT
000002ea: JUMPDEST
000002eb: PUSH1 0x40
000002ed: MLOAD
000002ee: PUSH4 0xd6bda275
000002f3: PUSH1 0xe0
000002f5: SHL
000002f6: DUP2
000002f7: MSTORE
000002f8: PUSH1 0x04
000002fa: ADD
000002fb: PUSH1 0x40
000002fd: MLOAD
000002fe: DUP1
000002ff: SWAP2
00000300: SUB
00000301: SWAP1
00000302: REVERT
00000303: JUMPDEST
00000304: PUSH0
00000305: DUP1
00000306: DUP6
00000307: DUP6
00000308: GT
00000309: ISZERO
0000030a: PUSH2 0x0311
0000030d: JUMPI
0000030e: PUSH0
0000030f: DUP1
00000310: REVERT
00000311: JUMPDEST
00000312: DUP4
00000313: DUP7
00000314: GT
00000315: ISZERO
00000316: PUSH2 0x031d
00000319: JUMPI
0000031a: PUSH0
0000031b: DUP1
0000031c: REVERT
0000031d: JUMPDEST
0000031e: POP
0000031f: POP
00000320: DUP3
00000321: ADD
00000322: SWAP4
00000323: SWAP2
00000324: SWAP1
00000325: SWAP3
00000326: SUB
00000327: SWAP2
00000328: POP
00000329: JUMP
0000032a: JUMPDEST
0000032b: PUSH4 0x4e487b71
00000330: PUSH1 0xe0
00000332: SHL
00000333: PUSH0
00000334: MSTORE
00000335: PUSH1 0x41
00000337: PUSH1 0x04
00000339: MSTORE
0000033a: PUSH1 0x24
0000033c: PUSH0
0000033d: REVERT
0000033e: JUMPDEST
0000033f: PUSH0
00000340: DUP1
00000341: PUSH1 0x40
00000343: DUP4
00000344: DUP6
00000345: SUB
00000346: SLT
00000347: ISZERO
00000348: PUSH2 0x034f
0000034b: JUMPI
0000034c: PUSH0
0000034d: DUP1
0000034e: REVERT
0000034f: JUMPDEST
00000350: DUP3
00000351: CALLDATALOAD
00000352: PUSH1 0x01
00000354: PUSH1 0x01
00000356: PUSH1 0xa0
00000358: SHL
00000359: SUB
0000035a: DUP2
0000035b: AND
0000035c: DUP2
0000035d: EQ
0000035e: PUSH2 0x0365
00000361: JUMPI
00000362: PUSH0
00000363: DUP1
00000364: REVERT
00000365: JUMPDEST
00000366: SWAP2
00000367: POP
00000368: PUSH1 0x20
0000036a: DUP4
0000036b: ADD
0000036c: CALLDATALOAD
0000036d: PUSH8 0xffffffffffffffff
00000376: DUP1
00000377: DUP3
00000378: GT
00000379: ISZERO
0000037a: PUSH2 0x0381
0000037d: JUMPI
0000037e: PUSH0
0000037f: DUP1
00000380: REVERT
00000381: JUMPDEST
00000382: DUP2
00000383: DUP6
00000384: ADD
00000385: SWAP2
00000386: POP
00000387: DUP6
00000388: PUSH1 0x1f
0000038a: DUP4
0000038b: ADD
0000038c: SLT
0000038d: PUSH2 0x0394
00000390: JUMPI
00000391: PUSH0
00000392: DUP1
00000393: REVERT
00000394: JUMPDEST
00000395: DUP2
00000396: CALLDATALOAD
00000397: DUP2
00000398: DUP2
00000399: GT
0000039a: ISZERO
0000039b: PUSH2 0x03a6
0000039e: JUMPI
0000039f: PUSH2 0x03a6
000003a2: PUSH2 0x032a
000003a5: JUMP
000003a6: JUMPDEST
000003a7: PUSH1 0x40
000003a9: MLOAD
000003aa: PUSH1 0x1f
000003ac: DUP3
000003ad: ADD
000003ae: PUSH1 0x1f
000003b0: NOT
000003b1: SWAP1
000003b2: DUP2
000003b3: AND
000003b4: PUSH1 0x3f
000003b6: ADD
000003b7: AND
000003b8: DUP2
000003b9: ADD
000003ba: SWAP1
000003bb: DUP4
000003bc: DUP3
000003bd: GT
000003be: DUP2
000003bf: DUP4
000003c0: LT
000003c1: OR
000003c2: ISZERO
000003c3: PUSH2 0x03ce
000003c6: JUMPI
000003c7: PUSH2 0x03ce
000003ca: PUSH2 0x032a
000003cd: JUMP
000003ce: JUMPDEST
000003cf: DUP2
000003d0: PUSH1 0x40
000003d2: MSTORE
000003d3: DUP3
000003d4: DUP2
000003d5: MSTORE
000003d6: DUP9
000003d7: PUSH1 0x20
000003d9: DUP5
000003da: DUP8
000003db: ADD
000003dc: ADD
000003dd: GT
000003de: ISZERO
000003df: PUSH2 0x03e6
000003e2: JUMPI
000003e3: PUSH0
000003e4: DUP1
000003e5: REVERT
000003e6: JUMPDEST
000003e7: DUP3
000003e8: PUSH1 0x20
000003ea: DUP7
000003eb: ADD
000003ec: PUSH1 0x20
000003ee: DUP4
000003ef: ADD
000003f0: CALLDATACOPY
000003f1: PUSH0
000003f2: PUSH1 0x20
000003f4: DUP5
000003f5: DUP4
000003f6: ADD
000003f7: ADD
000003f8: MSTORE
000003f9: DUP1
000003fa: SWAP6
000003fb: POP
000003fc: POP
000003fd: POP
000003fe: POP
000003ff: POP
00000400: POP
00000401: SWAP3
00000402: POP
00000403: SWAP3
00000404: SWAP1
00000405: POP
00000406: JUMP
00000407: JUMPDEST
00000408: PUSH0
00000409: DUP3
0000040a: MLOAD
0000040b: DUP1
0000040c: PUSH1 0x20
0000040e: DUP6
0000040f: ADD
00000410: DUP5
00000411: MCOPY
00000412: PUSH0
00000413: SWAP3
00000414: ADD
00000415: SWAP2
00000416: DUP3
00000417: MSTORE
00000418: POP
00000419: SWAP2
0000041a: SWAP1
0000041b: POP
0000041c: JUMP
0000041d: INVALID
0000041e: LOG2
0000041f: PUSH5 0x6970667358
00000425: UNKNOWN(0x22)
00000426: SLT
00000427: KECCAK256
00000428: UNKNOWN(0xD9)
00000429: PUSH31 0xaf6413661ec158cd95206e2d8b2d570512e88949f67b8e1a4f4c3cb9964464
00000449: PUSH20

Findings

validated = confirmed for call sinks; sink observed for others
Severity Category Tool Title Confidence Validated Confirmed Details
critical upgradeability detector Untrusted DELEGATECALL target reachable 70 no no
view
trace_id: 0x3fd0961cdc3a05ee1b14246cb5c532448b199b12800baca3f3ded73cf35eadf3
call_targets: 0x00000000d681e85e5783588f87a9573cb97eda01
target_varies: no
classification: constant_target
validation_json
{
    "sink": "DELEGATECALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x3fd0961cdc3a05ee1b14246cb5c532448b199b12800baca3f3ded73cf35eadf3",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0x00000000d681e85e5783588f87a9573cb97eda01"
    ],
    "matched_probe": null,
    "target_varies": false,
    "classification": "constant_target"
}
witness_json
{
    "notes": "heuristic witness; entrypoints are best-effort selectors with zeroed calldata",
    "constraints": [],
    "entrypoints": [
        {
            "calldata": "0x278f7943",
            "selector": "0x278f7943",
            "calldata_variants": [
                "0x278f7943",
                "0x278f79430000000000000000000000000000000000000000000000000000000000000000"
            ]
        }
    ]
}
evidence_json
{
    "examples": [
        {
            "pc": 348,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        },
        {
            "pc": 529,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        }
    ],
    "guards_detected": [],
    "delegatecall_count": 2,
    "untrusted_delegatecall_count": 2
}
high upgradeability codex Version-dependent storage slots can brick the proxy and strand assets on upgrade 85 no
view
evidence_json
{
    "evidence": "`SlotLibrary.getSlot` mixes `contractName`, `name`, and `version` into the slot seed. `MellowACL` and `SwapModule` cache those derived slots in immutables during construction and use them for all reads/writes of first-party state, so a new implementation with different constructor args silently points the proxy at a different storage region.",
    "locations": [
        "src/libraries/SlotLibrary.sol:13",
        "src/permissions/MellowACL.sol:11",
        "src/permissions/MellowACL.sol:58",
        "src/utils/SwapModule.sol:37",
        "src/utils/SwapModule.sol:62",
        "src/utils/SwapModule.sol:197",
        "src/utils/SwapModule.sol:306"
    ],
    "recommendation": "Do not derive live storage slots from constructor-time version/name inputs for proxy state. Use fixed ERC-7201/ERC-1967-style slots that remain identical across implementation versions, or enforce that every upgrade reuses the exact same slot constants.",
    "exploit_scenario": "The proxy points at this implementation today. If the proxy admin later upgrades to a new `SwapModule` implementation deployed with a different `name_` or `version_`, the immutables `_swapModuleStorageSlot` and `_mellowACLStorageSlot` change. After the upgrade, `subvault()`, `oracle()`, multipliers, and supported-role bookkeeping are read from empty storage. Because `subvault` has no setter and `initialize` is one-shot, the proxy can no longer satisfy `onlySubvault`, leaving assets held by the proxy permanently stuck."
}
medium upgradeability cast DELEGATECALL present 80 no
view
evidence_json
{
    "notes": "delegatecall enables execution in caller context; common in proxies/upgradeable patterns",
    "delegatecall_count": 2
}
medium access_control codex `createLimitOrder` does not constrain CoWSwap `feeAmount`, so CALLER_ROLE can spend more than `params.amountIn` 75 no
view
evidence_json
{
    "evidence": "`checkCowswapOrder` validates token addresses, `sellAmount`, `buyAmount`, deadline, kind, and balance modes, but never checks `order.feeAmount` (or `order.partiallyFillable`). Separately, `setCowswapApproval` allows arbitrary allowance to `cowswapVaultRelayer`, so settlement can legally transfer more sell token than `params.amountIn`.",
    "locations": [
        "src/utils/SwapModule.sol:137",
        "src/utils/SwapModule.sol:150",
        "src/utils/SwapModule.sol:153",
        "src/utils/SwapModule.sol:277",
        "src/utils/SwapModule.sol:286"
    ],
    "recommendation": "Reject orders with nonzero `feeAmount`, or explicitly include fee in the validated parameters and in the balance/allowance accounting. If partial fills are not needed, also require `order.partiallyFillable == false` and scope approval to the exact live exposure instead of a reusable allowance.",
    "exploit_scenario": "A compromised or over-scoped `CALLER_ROLE` can first call `setCowswapApproval(asset, type(uint256).max)`, then create a pre-signed CoWSwap order with `sellAmount == params.amountIn` but an arbitrarily large `feeAmount`. The order passes `checkParams` and `checkCowswapOrder`, yet CoWSwap settlement will pull `sellAmount + feeAmount` from the module. The extra sell token is outside the min-out/slippage checks, so approved balances can be drained far beyond the amount the module intended to trade."
}