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

Analysis Run

019d5666-7204-7199-9428-87c933f7acea

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
42841 artifact.fetch complete 1 / 3 1 week ago
42842 proxy.resolve complete 1 / 3 1 week ago
42843 tool.cast_disasm complete 1 / 3 1 week ago
42844 analysis.bundle complete 1 / 3 1 week ago
42845 capability.graph complete 1 / 3 1 week ago
42846 detector.run complete 1 / 3 1 week ago
42847 validation.fork complete 1 / 3 1 week ago
42957 report.consolidate complete 1 / 6 1 week ago
42958 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": "0x6ac64c4760e0590f88233b2046810e87e0354324"
        }
    },
    "implementation": "0x6ac64c4760e0590f88233b2046810e87e0354324",
    "onchain_method": "eip1967"
}

Slither

tool.slither
Status
failed slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-llgbznxh) 'forge config --json' running 'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-llgbznxh) 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-llgbznxh/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-llgbznxh)
'forge config --json' running
'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-llgbznxh)
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-llgbznxh/out/build-info is not a directory.

Codex

report.consolidate
Job Status
complete
Attempts
1 / 6
Findings
3
No findings?
no
Summary
Found three meaningful issues in the implementation: `fillOffer` is reentrant before state is updated, ERC20 collateral is accounted at nominal rather than received amounts, and the owner can de-whitelist then withdraw live escrowed tokens.
View stdout
{"no_findings":false,"summary":"Found three meaningful issues in the implementation: `fillOffer` is reentrant before state is updated, ERC20 collateral is accounted at nominal rather than received amounts, and the owner can de-whitelist then withdraw live escrowed tokens.","findings":[{"severity":"high","category":"reentrancy","title":"`fillOffer` can be reentered before `filledAmount` is updated, allowing overfilled orders","exploit_scenario":"If a whitelisted collateral token invokes callbacks during `transferFrom` (for example an ERC777-style token or a callback-capable ERC20), an attacker can fill the same offer reentrantly from a contract wallet. Each nested call observes the old `offer.filledAmount`, so multiple full-sized orders are created against one offer. The attacker can then self-settle those duplicated orders and extract more `value + collateral` than was originally escrowed, draining pooled liquidity or leaving the market insolvent.","recommendation":"Make `fillOffer` non-reentrant, or expose only a guarded external entrypoint and keep the core logic internal. Update `offer.filledAmount`/status before any external token transfer, or otherwise re-check remaining capacity after the transfer returns.","evidence":"`fillOffer` performs `iexToken.safeTransferFrom(...)` before calling `_fillOffer(...)`. `_fillOffer` is the only place that increments `offer.filledAmount` and closes the offer.","locations":["src/FrontrunMarket.sol:381","src/FrontrunMarket.sol:410","src/FrontrunMarket.sol:412","src/FrontrunMarket.sol:763","src/FrontrunMarket.sol:770"]},{"severity":"high","category":"other","title":"Nominal ERC20 accounting makes fee-on-transfer collateral tokens insolvent","exploit_scenario":"If governance ever accepts a deflationary / fee-on-transfer token as collateral, the market records `value` and `collateral` using the requested amounts instead of the actual tokens received. An attacker can self-trade using that token so the contract receives less than it promises to pay back on cancel / settle. The shortfall is then paid from unrelated users' escrow if available, or later exits revert because the vault is underfunded.","recommendation":"Only allow plain ERC20 collateral tokens, or account using balance deltas before and after each transfer. Reject deposits where the actual received amount is smaller than the expected amount.","evidence":"Both `newOffer` and `fillOffer` transfer ERC20s and then keep using caller-supplied `value` / derived `collateral`; no post-transfer balance check exists before later payouts in `forceCancelOrder`, `settleFilled`, and `settleCancelled`.","locations":["src/FrontrunMarket.sol:322","src/FrontrunMarket.sol:343","src/FrontrunMarket.sol:381","src/FrontrunMarket.sol:410","src/FrontrunMarket.sol:526","src/FrontrunMarket.sol:575"]},{"severity":"medium","category":"access_control","title":"Owner can confiscate live escrow by de-whitelisting a token before `withdrawStuckToken`","exploit_scenario":"While users still have open offers or orders denominated in token `X`, the owner can call `setAcceptedTokens([X], false)` and immediately follow with `withdrawStuckToken(X, owner)`. Because the withdrawal gate only checks the current whitelist bit, the function transfers the entire balance of `X`, including funds backing active positions. Those positions then remain recorded on-chain but cannot be settled or cancelled successfully.","recommendation":"Track escrowed balances per collateral token and forbid withdrawing any amount backing live offers/orders. `withdrawStuckToken` should not rely only on the current accepted-token flag.","evidence":"`setAcceptedTokens` can flip any token's accepted status at any time, and `withdrawStuckToken` transfers the full contract balance whenever `acceptedTokens[_token]` is false.","locations":["src/FrontrunMarket.sol:656","src/FrontrunMarket.sol:660","src/FrontrunMarket.sol:780","src/FrontrunMarket.sol:782","src/FrontrunMarket.sol:784"]}]}
View stderr
OpenAI Codex v0.118.0 (research preview)
--------
workdir: /tmp/codex-dgrhxd3v
model: gpt-5.4
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: none
session id: 019d568a-c3cb-7493-b929-b368acd8f7d9
--------
user
Context: The user requested the proxy at 0x849f4081899305a1fd24aac84db5174eb60dc28e.
Audit the implementation at 0x6ac64c4760e0590f88233b2046810e87e0354324.
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-dgrhxd3v
 succeeded in 0ms:
/tmp/codex-dgrhxd3v

exec
/bin/bash -lc 'rg --files' in /tmp/codex-dgrhxd3v
 succeeded in 0ms:
codex-output-schema.json
src/FrontrunMarket.sol
AUDIT_BUNDLE.txt
lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol
lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol
lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol
lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol
lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol
lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol
lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol
lib/openzeppelin-contracts/contracts/utils/Address.sol
lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol
lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol
lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
lib/openzeppelin-contracts/contracts/access/IAccessControl.sol
lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol
lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol
lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol

exec
/bin/bash -lc "sed -n '1,260p' AUDIT_BUNDLE.txt" in /tmp/codex-dgrhxd3v
 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/FrontrunMarket.sol
================================================================================
//SPDX-License-Identifier: Unlicense
pragma solidity 0.8.20;

import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import '@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol';
import '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';

struct Token {
  address token;
  uint48 settleTime;
  uint48 settleDuration;
  uint152 settleRate; // number of token per point
  uint8 status; //
  uint256 minAmount;
}

struct Offer {
  uint8 offerType;
  bytes32 tokenId;
  address exToken;
  uint256 amount;
  uint256 value;
  uint256 collateral;
  uint256 filledAmount;
  uint8 status;
  address offeredBy;
  uint256 minAmount;
}

struct Order {
  uint256 offerId;
  uint256 amount;
  address seller;
  address buyer;
  uint8 status;
}

struct Config {
  uint256 pledgeRate;
  uint256 feeRefund;
  uint256 feeSettle;
  address feeWallet;
}

contract FrontrunMarket is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable {
  using SafeERC20 for IERC20;

  uint256 constant WEI6 = 10 ** 6;
  uint8 constant OFFER_BUY = 1;
  uint8 constant OFFER_SELL = 2;

  // Status
  // Offer status
  uint8 constant STATUS_OFFER_OPEN = 1;
  uint8 constant STATUS_OFFER_FILLED = 2;
  uint8 constant STATUS_OFFER_CANCELLED = 3;

  // Order Status
  uint8 constant STATUS_ORDER_OPEN = 1;
  uint8 constant STATUS_ORDER_SETTLE_FILLED = 2;
  uint8 constant STATUS_ORDER_SETTLE_CANCELLED = 3;
  uint8 constant STATUS_ORDER_CANCELLED = 4;

  // token status
  uint8 constant STATUS_TOKEN_ACTIVE = 1;
  uint8 constant STATUS_TOKEN_INACTIVE = 2;
  uint8 constant STATUS_TOKEN_SETTLE = 3;

  struct MarketStorage {
    mapping(address => bool) acceptedTokens;
    mapping(bytes32 => Token) tokens;
    mapping(uint256 => Offer) offers;
    uint256 lastOfferId;
    mapping(uint256 => Order) orders;
    uint256 lastOrderId;
    Config config;
  }

  //keccak256(abi.encode(uint256(keccak256("frontrun.Market")) - 1)) & ~bytes32(uint256(0xff));
  bytes32 constant MarketStorageLocation = 0x02c326031f2f3dedd13e7fde59c7c20af05a8826f4c07e8d42538c5f2be06800;

  function _getOwnStorage() private pure returns (MarketStorage storage $) {
    assembly {
      $.slot := MarketStorageLocation
    }
  }

  // event

  event NewOffer(
    uint256 id,
    uint8 offerType,
    bytes32 tokenId,
    address exToken,
    uint256 amount,
    uint256 value,
    uint256 collateral,
    uint256 minAmount,
    address doer
  );
  event NewToken(bytes32 tokenId, uint256 settleDuration, uint256 minAmount);
  event NewOrder(uint256 id, uint256 offerId, uint256 amount, address seller, address buyer);

  event SettleFilled(uint256 orderId, uint256 value, uint256 fee, address doer);
  event SettleCancelled(uint256 orderId, uint256 value, uint256 fee, address doer);

  event CancelOrder(uint256 orderId, address doer);
  event CancelOffer(uint256 offerId, uint256 refundValue, uint256 refundFee, address doer);

  event UpdateAcceptedTokens(address[] tokens, bool isAccepted);

  event CloseOffer(uint256 offerId, uint256 refundAmount);

  event UpdateConfig(
    address oldFeeWallet,
    uint256 oldFeeSettle,
    uint256 oldFeeRefund,
    uint256 oldPledgeRate,
    address newFeeWallet,
    uint256 newFeeSettle,
    uint256 newFeeRefund,
    uint256 newPledgeRate
  );

  event TokenToSettlePhase(bytes32 tokenId, address token, uint256 settleRate, uint256 settleTime);
  event UpdateTokenStatus(bytes32 tokenId, uint8 oldValue, uint8 newValue);
  event TokenForceCancelSettlePhase(bytes32 tokenId);

  event Settle2Steps(uint256 orderId, bytes32 hash, address doer);

  event UpdateTokenSettleDuration(bytes32 tokenId, uint48 oldValue, uint48 newValue);

  function initialize() external initializer {
    __Ownable_init(msg.sender);
    // init value
    MarketStorage storage $ = _getOwnStorage();
    $.config.pledgeRate = WEI6; //
    $.config.feeWallet = owner();
    $.config.feeSettle = WEI6 / 200; // 0.5%
    $.config.feeRefund = 0; // 0.0%
  }

  ///////////////////////////
  ////// SYSTEM ACTION //////
  ///////////////////////////

  /// @notice Creates a market to trade
  /// @param tokenId market identifier
  /// @param settleDuration Duration after which buyer of token can claim collateral if seller hasnt delivered
  /// @param minAmount minAmount that can be filled for this particular market including offers and orders , 6 decimals
  // add min order value in market so that in settle2steps we dont have to pay gas to settle low value orders
  function createToken(bytes32 tokenId, uint48 settleDuration, uint256 minAmount) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    Token storage _token = $.tokens[tokenId];
    _token.settleDuration = settleDuration;
    _token.minAmount = minAmount;
    _token.status = STATUS_TOKEN_ACTIVE;
    emit NewToken(tokenId, settleDuration, minAmount);
  }

  /// @notice moves a market to settlement phase
  /// @param tokenId market identifier
  /// @param tokenAddress contract address for token to be delivered
  /// @param settleRate how many tokens per 1M amount traded(if token to be delivered has 18 decimals write 1e12)
  function tokenToSettlePhase(
    bytes32 tokenId,
    address tokenAddress,
    uint152 settleRate // how many token for 1M points
  ) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    Token storage _token = $.tokens[tokenId];
    require(tokenAddress != address(0), 'Invalid Token Address');
    require(settleRate > 0, 'Invalid Settle Rate');
    require(_token.status == STATUS_TOKEN_ACTIVE || _token.status == STATUS_TOKEN_INACTIVE, 'Invalid Token Status');
    _token.token = tokenAddress;
    _token.settleRate = settleRate;
    // update token settle status & time
    _token.status = STATUS_TOKEN_SETTLE;
    _token.settleTime = uint48(block.timestamp);

    emit TokenToSettlePhase(tokenId, tokenAddress, settleRate, block.timestamp);
  }

  /// @notice toggle market active inactive
  /// @param tokenId market identifier
  function tokenToggleActivation(bytes32 tokenId) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    Token storage _token = $.tokens[tokenId];
    uint8 fromStatus = _token.status;
    uint8 toStatus = fromStatus == STATUS_TOKEN_ACTIVE ? STATUS_TOKEN_INACTIVE : STATUS_TOKEN_ACTIVE;

    require(fromStatus == STATUS_TOKEN_ACTIVE || fromStatus == STATUS_TOKEN_INACTIVE, 'Cannot Change Token Status');

    _token.status = toStatus;
    emit UpdateTokenStatus(tokenId, fromStatus, toStatus);
  }

  /// @notice  // in case wrong setting for settle
  /// @param tokenId market identifier
  function tokenForceCancelSettlePhase(bytes32 tokenId) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    Token storage _token = $.tokens[tokenId];
    require(_token.status == STATUS_TOKEN_SETTLE, 'Invalid Token Status');
    _token.status = STATUS_TOKEN_INACTIVE;
    emit TokenForceCancelSettlePhase(tokenId);
  }
  /// @notice  // updates settleDuration
  /// @param tokenId market identifier
  /// @param newValue settleDuration value

  function updateSettleDuration(bytes32 tokenId, uint48 newValue) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    Token storage _token = $.tokens[tokenId];
    uint48 oldValue = _token.settleDuration;
    _token.settleDuration = newValue;
    emit UpdateTokenSettleDuration(tokenId, oldValue, newValue);
  }

  // force cancel order - by Operator/Owner
  // refund for both seller & buyer without any fees cut
  /// @notice
  /// @param orderId order identifier
  function forceCancelOrder(uint256 orderId) public nonReentrant onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    Order storage order = $.orders[orderId];
    Offer storage offer = $.offers[order.offerId];

    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');

    // calculate refund
    uint256 buyerRefundValue = (order.amount * offer.value) / offer.amount; // value
    uint256 sellerRefundValue = (order.amount * offer.collateral) / offer.amount; // collateral
    address buyer = order.buyer;
    address seller = order.seller;

    // refund
    if (offer.exToken == address(0)) {
      // refund ETH
      if (buyerRefundValue > 0 && buyer != address(0)) {
        (bool success,) = buyer.call{value: buyerRefundValue}('');
        require(success, 'Transfer Funds to Seller Fail');
      }
      if (sellerRefundValue > 0 && seller != address(0)) {
        (bool success,) = seller.call{value: sellerRefundValue}('');
        require(success, 'Transfer Funds to Seller Fail');
      }
    } else {
      IERC20 iexToken = IERC20(offer.exToken);
      if (buyerRefundValue > 0 && buyer != address(0)) {

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

exec
/bin/bash -lc "sed -n '261,520p' AUDIT_BUNDLE.txt" in /tmp/codex-dgrhxd3v
 succeeded in 0ms:
        iexToken.safeTransfer(buyer, buyerRefundValue);
      }
      if (sellerRefundValue > 0 && seller != address(0)) {
        iexToken.safeTransfer(seller, sellerRefundValue);
      }
    }
    order.status = STATUS_ORDER_CANCELLED;
    emit CancelOrder(orderId, msg.sender);
  }

  // 2 steps settle:
  // Tx1: Seller sending token to system vault/buyer
  // Tx2: then Operator/Owner verify and settle to pay seller money+collateral
  function settle2Steps(uint256 orderId, bytes32 hash) public nonReentrant onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    Order storage order = $.orders[orderId];
    Offer storage offer = $.offers[order.offerId];
    Token storage token = $.tokens[offer.tokenId];

    // check condition
    require(token.status == STATUS_TOKEN_SETTLE, 'Invalid Status');
    require(token.token != address(0) && token.settleRate > 0, 'Token Not Set');
    require(block.timestamp > token.settleTime, 'Settling Time Not Started');
    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');

    uint256 collateral = (order.amount * offer.collateral) / offer.amount;
    uint256 value = (order.amount * offer.value) / offer.amount;

    // transfer liquid to seller
    uint256 settleFee = (value * $.config.feeSettle) / WEI6;
    uint256 totalValue = value + collateral - settleFee;
    if (offer.exToken == address(0)) {
      // by ETH
      (bool success1,) = order.seller.call{value: totalValue}('');
      (bool success2,) = $.config.feeWallet.call{value: settleFee}('');
      require(success1 && success2, 'Transfer Funds Fail');
    } else {
      // by exToken
      IERC20 iexToken = IERC20(offer.exToken);
      iexToken.safeTransfer(order.seller, totalValue);
      iexToken.safeTransfer($.config.feeWallet, settleFee);
    }

    order.status = STATUS_ORDER_SETTLE_FILLED;

    emit Settle2Steps(orderId, hash, msg.sender);
    emit SettleFilled(orderId, totalValue, settleFee, msg.sender);
  }

  function settle2StepsBatch(uint256[] calldata orderIds, bytes32[] calldata hashes) external {
    require(orderIds.length == hashes.length, 'Invalid Input');
    for (uint256 i = 0; i < orderIds.length; i++) {
      settle2Steps(orderIds[i], hashes[i]);
    }
  }

  /////////////////////////
  ////// USER ACTION //////
  /////////////////////////

  // make a offer request using erc20 collateral
  /// @notice
  /// @param tokenId market identifier
  /// @param amount amount of market asset in 6 decimals
  /// @param value amount of collateral being deposited, price = collateral/amount
  /// @param exToken collateral token contract addres
  /// @param minAmount min amount that must be filled by the counterparty in 6 decimals
  function newOffer(
    uint8 offerType,
    bytes32 tokenId,
    uint256 amount, //amount of asset
    uint256 value, // amount of collateral
    address exToken,
    uint256 minAmount
  ) external nonReentrant {
    MarketStorage storage $ = _getOwnStorage();
    Token storage token = $.tokens[tokenId];
    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid Token');
    require(exToken != address(0) && $.acceptedTokens[exToken], 'Invalid Offer Token');
    require(amount > 0 && value > 0, 'Invalid Amount or Value');
    require(amount >= minAmount, 'minamount to be filled cant be greater then amount');
    require(minAmount >= token.minAmount, 'minamount should be greater then eual to market global minamount');
    IERC20 iexToken = IERC20(exToken);
    // collateral
    uint256 collateral = (value * $.config.pledgeRate) / WEI6;

    // transfer offer value (offer buy) or collateral (offer sell)
    uint256 _transferAmount = offerType == OFFER_BUY ? value : collateral;
    iexToken.safeTransferFrom(msg.sender, address(this), _transferAmount);

    // create new offer
    _newOffer(offerType, tokenId, exToken, amount, value, collateral, minAmount);
  }

  // New offer in ETH
  /// @notice
  /// @param tokenId market identifier
  /// @param amount amount of market asset in 6 decimals
  /// @param value amount of collateral being deposited, price = collateral/amount
  /// @param minAmount min amount that must be filled by the counterparty in 6 decimals
  function newOfferETH(
    uint8 offerType,
    bytes32 tokenId,
    uint256 amount,
    uint256 value,
    uint256 minAmount
  ) external payable nonReentrant {
    MarketStorage storage $ = _getOwnStorage();
    Token storage token = $.tokens[tokenId];
    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid Token');
    require(amount > 0 && value > 0, 'Invalid Amount or Value');
    require(amount >= minAmount, 'minamount to be filled cant be greater then amount');
    require(minAmount >= token.minAmount, 'minamount should be greater then eual to market global minamount');

    // collateral
    uint256 collateral = (value * $.config.pledgeRate) / WEI6;

    uint256 _ethAmount = offerType == OFFER_BUY ? value : collateral;
    require(_ethAmount == msg.value, 'Insufficient Funds');
    // create new offer
    _newOffer(offerType, tokenId, address(0), amount, value, collateral, minAmount);
  }

  /// @notice fill an offer with erc20 collateral and generate an orderid associated with a trade
  /// @param offerId offer identifier
  /// @param amount amount of market asset in 6 decimals
  function fillOffer(uint256 offerId, uint256 amount) public {
    MarketStorage storage $ = _getOwnStorage();
    Offer storage offer = $.offers[offerId];
    Token storage token = $.tokens[offer.tokenId];
    require(offer.status == STATUS_OFFER_OPEN, 'Invalid Offer Status');
    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid token Status');
    require(amount > 0, 'Invalid Amount');
    require(offer.amount - offer.filledAmount >= amount, 'Insufficient Allocations');
    if (offer.amount - offer.filledAmount > offer.minAmount) {
      require(amount >= offer.minAmount, 'cannot fill less then offer minamount');
    } else {
      require(amount == offer.amount - offer.filledAmount, 'if less then asset minamount must fill complete order');
    }
    require(offer.exToken != address(0), 'Invalid Offer Token');

    // transfer value or collecteral
    IERC20 iexToken = IERC20(offer.exToken);
    uint256 _transferAmount;
    address buyer;
    address seller;
    if (offer.offerType == OFFER_BUY) {
      _transferAmount = (offer.collateral * amount) / offer.amount;
      buyer = offer.offeredBy;
      seller = msg.sender;
    } else {
      _transferAmount = (offer.value * amount) / offer.amount;
      buyer = msg.sender;
      seller = offer.offeredBy;
    }
    iexToken.safeTransferFrom(msg.sender, address(this), _transferAmount);

    _fillOffer(offerId, amount, buyer, seller);
  }

  /// @notice fill an offer with eth collateral and generate an orderid associated with a trade
  /// @param offerId offer identifier
  /// @param amount amount of market asset in 6 decimals
  function fillOfferETH(uint256 offerId, uint256 amount) external payable nonReentrant {
    uint256 _ethAmount = _fillOfferETH(offerId, amount);
    require(msg.value >= _ethAmount, 'Insufficient Funds');
    require(_ethAmount + 5 * 1e16 > msg.value, 'Extra Amount Sent');
  }

  /// @notice batche fill both types of offers
  function batchFillOffer(uint256[] calldata offerId, uint256[] calldata amount) external payable nonReentrant {
    require(offerId.length == amount.length, 'Invalid Input');
    uint256 _ethAmount;
    MarketStorage storage $ = _getOwnStorage();
    for (uint256 i = 0; i < offerId.length; i++) {
      Offer storage offer = $.offers[offerId[i]];
      if (offer.exToken == address(0)) {
        _ethAmount += _fillOfferETH(offerId[i], amount[i]);
      } else {
        fillOffer(offerId[i], amount[i]);
      }
    }
    require(msg.value >= _ethAmount, 'Insufficient Funds');
    require(_ethAmount + 5 * 1e16 > msg.value, 'Extra Amount Sent');
  }

  function _fillOfferETH(uint256 offerId, uint256 amount) internal returns (uint256 _ethAmount) {
    MarketStorage storage $ = _getOwnStorage();
    Offer storage offer = $.offers[offerId];
    Token storage token = $.tokens[offer.tokenId];
    require(offer.status == STATUS_OFFER_OPEN, 'Invalid Offer Status');
    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid token Status');
    require(amount > 0, 'Invalid Amount');
    require(offer.amount - offer.filledAmount >= amount, 'Insufficient Allocations');
    if (offer.amount - offer.filledAmount > offer.minAmount) {
      require(amount >= offer.minAmount, 'cannot fill less then offer minamount or asset minamount');
    } else {
      require(amount == offer.amount - offer.filledAmount, 'if less then asset minamount must fill complete order');
    }
    require(offer.exToken == address(0), 'Invalid Offer Token');

    // transfer value or collecteral
    address buyer;
    address seller;
    if (offer.offerType == OFFER_BUY) {
      _ethAmount = (offer.collateral * amount) / offer.amount;
      buyer = offer.offeredBy;
      seller = msg.sender;
    } else {
      _ethAmount = (offer.value * amount) / offer.amount;
      buyer = msg.sender;
      seller = offer.offeredBy;
    }
    // new order
    _fillOffer(offerId, amount, buyer, seller);
  }

  /// @notice close unfullfilled offer - by Offer owner
  /// @param offerId offer identifier
  function cancelOffer(uint256 offerId) public nonReentrant {
    MarketStorage storage $ = _getOwnStorage();
    Offer storage offer = $.offers[offerId];

    require(offer.offeredBy == msg.sender, 'Offer Owner Only');
    require(offer.status == STATUS_OFFER_OPEN, 'Invalid Offer Status');

    uint256 refundAmount = offer.amount - offer.filledAmount;
    require(refundAmount > 0, 'Insufficient Allocations');

    // calculate refund
    uint256 refundValue;
    if (offer.offerType == OFFER_BUY) {
      refundValue = (refundAmount * offer.value) / offer.amount;
    } else {
      refundValue = (refundAmount * offer.collateral) / offer.amount;
    }
    uint256 refundFee = (refundValue * $.config.feeRefund) / WEI6;
    refundValue -= refundFee;

    // refund
    if (offer.exToken == address(0)) {
      // refund ETH
      (bool success1,) = offer.offeredBy.call{value: refundValue}('');
      (bool success2,) = $.config.feeWallet.call{value: refundFee}('');
      require(success1 && success2, 'Transfer Funds Fail');
    } else {
      IERC20 iexToken = IERC20(offer.exToken);
      iexToken.safeTransfer(offer.offeredBy, refundValue);
      iexToken.safeTransfer($.config.feeWallet, refundFee);
    }

    offer.status = STATUS_OFFER_CANCELLED;
    emit CancelOffer(offerId, refundValue, refundFee, msg.sender);
  }

  //
  /// @notice settle order - deliver token to finillize the order
  /// @param orderId order identifier
  function settleFilled(uint256 orderId) public nonReentrant {
    MarketStorage storage $ = _getOwnStorage();

exec
/bin/bash -lc "sed -n '521,793p' AUDIT_BUNDLE.txt" in /tmp/codex-dgrhxd3v
 succeeded in 0ms:
    Order storage order = $.orders[orderId];
    Offer storage offer = $.offers[order.offerId];
    Token storage token = $.tokens[offer.tokenId];

    // check condition
    require(token.status == STATUS_TOKEN_SETTLE, 'Invalid Status');
    require(token.token != address(0) && token.settleRate > 0, 'Token Not Set');
    require(block.timestamp > token.settleTime, 'Settling Time Not Started');
    require(order.seller == msg.sender, 'Seller Only');
    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');

    uint256 collateral = (order.amount * offer.collateral) / offer.amount;
    uint256 value = (order.amount * offer.value) / offer.amount;

    // transfer token to buyer
    IERC20 iToken = IERC20(token.token);
    // calculate token amount base on it's decimals
    uint256 tokenAmount = (order.amount * token.settleRate) / WEI6;
    uint256 tokenAmountFee = (tokenAmount * $.config.feeSettle) / WEI6;
    // transfer order fee in token to fee wallet
    iToken.safeTransferFrom(order.seller, $.config.feeWallet, tokenAmountFee);
    // transfer token after fee to buyer
    iToken.safeTransferFrom(order.seller, order.buyer, tokenAmount - tokenAmountFee);

    // transfer liquid to seller
    uint256 settleFee = (value * $.config.feeSettle) / WEI6;
    uint256 totalValue = value + collateral - settleFee;
    if (offer.exToken == address(0)) {
      // by ETH
      (bool success1,) = order.seller.call{value: totalValue}('');
      (bool success2,) = $.config.feeWallet.call{value: settleFee}('');
      require(success1 && success2, 'Transfer Funds Fail');
    } else {
      // by exToken
      IERC20 iexToken = IERC20(offer.exToken);
      iexToken.safeTransfer(order.seller, totalValue);
      iexToken.safeTransfer($.config.feeWallet, settleFee);
    }

    order.status = STATUS_ORDER_SETTLE_FILLED;

    emit SettleFilled(orderId, totalValue, settleFee, msg.sender);
  }

  /// @notice cancel unfilled order by token buyer after settleDuration time frame
  /// @notice   // token seller lose collateral to token buyer
  /// @param orderId order identifier

  function settleCancelled(uint256 orderId) public nonReentrant {
    MarketStorage storage $ = _getOwnStorage();
    Order storage order = $.orders[orderId];
    Offer storage offer = $.offers[order.offerId];
    Token storage token = $.tokens[offer.tokenId];

    // check condition
    require(token.status == STATUS_TOKEN_SETTLE, 'Invalid Status');
    require(block.timestamp > token.settleTime + token.settleDuration, 'Settling Time Not Ended Yet');
    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');
    require(order.buyer == msg.sender || owner() == msg.sender, 'Buyer or Operator/Owner Only');

    uint256 collateral = (order.amount * offer.collateral) / offer.amount;
    uint256 value = (order.amount * offer.value) / offer.amount;

    // transfer liquid to buyer
    uint256 settleFee = (collateral * $.config.feeSettle * 2) / WEI6;
    uint256 totalValue = value + collateral - settleFee;
    if (offer.exToken == address(0)) {
      // by ETH
      (bool success1,) = order.buyer.call{value: totalValue}('');
      (bool success2,) = $.config.feeWallet.call{value: settleFee}('');
      require(success1 && success2, 'Transfer Funds Fail');
    } else {
      // by exToken
      IERC20 iexToken = IERC20(offer.exToken);
      iexToken.safeTransfer(order.buyer, totalValue);
      iexToken.safeTransfer($.config.feeWallet, settleFee);
    }

    order.status = STATUS_ORDER_SETTLE_CANCELLED;

    emit SettleCancelled(orderId, totalValue, settleFee, msg.sender);
  }

  // Batch actions
  function batchForceCancelOrders(uint256[] calldata orderIds) external {
    for (uint256 i = 0; i < orderIds.length; i++) {
      forceCancelOrder(orderIds[i]);
    }
  }

  function batchCancelOffers(uint256[] calldata offerIds) external {
    for (uint256 i = 0; i < offerIds.length; i++) {
      cancelOffer(offerIds[i]);
    }
  }

  function batchSettleFilleds(uint256[] calldata orderIds) external {
    for (uint256 i = 0; i < orderIds.length; i++) {
      settleFilled(orderIds[i]);
    }
  }

  function batchSettleCancelleds(uint256[] calldata orderIds) external {
    for (uint256 i = 0; i < orderIds.length; i++) {
      settleCancelled(orderIds[i]);
    }
  }

  ///////////////////////////
  ///////// SETTER //////////
  ///////////////////////////

  function updateConfig(
    address feeWallet_,
    uint256 feeSettle_,
    uint256 feeRefund_,
    uint256 pledgeRate_
  ) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    require(feeWallet_ != address(0), 'Invalid Address');
    require(feeSettle_ <= WEI6 / 100, 'Settle Fee <= 10%');
    require(feeRefund_ <= WEI6 / 100, 'Cancel Fee <= 10%');
    emit UpdateConfig(
      $.config.feeWallet,
      $.config.feeSettle,
      $.config.feeRefund,
      $.config.pledgeRate,
      feeWallet_,
      feeSettle_,
      feeRefund_,
      pledgeRate_
    );
    // update
    $.config.feeWallet = feeWallet_;
    $.config.feeSettle = feeSettle_;
    $.config.feeRefund = feeRefund_;
    $.config.pledgeRate = pledgeRate_;
  }

  //
  /// @notice adding/removing asset to be allowed to use as collateral
  function setAcceptedTokens(address[] calldata tokenAddresses, bool isAccepted) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();

    for (uint256 i = 0; i < tokenAddresses.length; i++) {
      $.acceptedTokens[tokenAddresses[i]] = isAccepted;
    }
    emit UpdateAcceptedTokens(tokenAddresses, isAccepted);
  }

  ///////////////////////////
  ///////// GETTER //////////
  ///////////////////////////
  function offerAmount(uint256 offerId) external view returns (uint256) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[offerId].amount;
  }

  function offerAmountAvailable(uint256 offerId) external view returns (uint256) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[offerId].amount - $.offers[offerId].filledAmount;
  }

  function offerValue(uint256 offerId) external view returns (uint256) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[offerId].value;
  }

  function offerExToken(uint256 offerId) external view returns (address) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[offerId].exToken;
  }

  function isBuyOffer(uint256 offerId) external view returns (bool) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[offerId].offerType == OFFER_BUY;
  }

  function isSellOffer(uint256 offerId) external view returns (bool) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[offerId].offerType == OFFER_SELL;
  }

  function offerStatus(uint256 offerId) external view returns (uint256) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[offerId].status;
  }

  function orderStatus(uint256 orderId) external view returns (uint256) {
    MarketStorage storage $ = _getOwnStorage();
    return $.orders[orderId].status;
  }

  function tokens(bytes32 tokenId) external view returns (Token memory) {
    MarketStorage storage $ = _getOwnStorage();
    return $.tokens[tokenId];
  }

  function offers(uint256 id) external view returns (Offer memory) {
    MarketStorage storage $ = _getOwnStorage();
    return $.offers[id];
  }

  function orders(uint256 id) external view returns (Order memory) {
    MarketStorage storage $ = _getOwnStorage();
    return $.orders[id];
  }

  function config() external view returns (Config memory) {
    MarketStorage storage $ = _getOwnStorage();
    return $.config;
  }

  function isAcceptedToken(address token) external view returns (bool) {
    MarketStorage storage $ = _getOwnStorage();
    return $.acceptedTokens[token];
  }

  function lastOfferId() external view returns (uint256) {
    MarketStorage storage $ = _getOwnStorage();
    return $.lastOfferId;
  }

  function lastOrderId() external view returns (uint256) {
    MarketStorage storage $ = _getOwnStorage();
    return $.lastOrderId;
  }

  ///////////////////////////
  //////// INTERNAL /////////
  ///////////////////////////
  function _newOffer(
    uint8 offerType,
    bytes32 tokenId,
    address exToken,
    uint256 amount,
    uint256 value,
    uint256 collateral,
    uint256 minAmount
  ) internal {
    MarketStorage storage $ = _getOwnStorage();
    // create new offer
    $.offers[++$.lastOfferId] =
      Offer(offerType, tokenId, exToken, amount, value, collateral, 0, STATUS_OFFER_OPEN, msg.sender, minAmount);

    emit NewOffer($.lastOfferId, offerType, tokenId, exToken, amount, value, collateral, minAmount, msg.sender);
  }

  function _fillOffer(uint256 offerId, uint256 amount, address buyer, address seller) internal {
    MarketStorage storage $ = _getOwnStorage();
    Offer storage offer = $.offers[offerId];
    // new order
    $.orders[++$.lastOrderId] = Order(offerId, amount, seller, buyer, STATUS_ORDER_OPEN);

    // check if offer is fullfilled
    offer.filledAmount += amount;
    if (offer.filledAmount == offer.amount) {
      offer.status = STATUS_OFFER_FILLED;
      emit CloseOffer(offerId, 0);
    }

    emit NewOrder($.lastOrderId, offerId, amount, seller, buyer);
  }

  // get stuck token in contract
  function withdrawStuckToken(address _token, address _to) external onlyOwner {
    MarketStorage storage $ = _getOwnStorage();
    require(_token != address(0) && !$.acceptedTokens[_token], 'Invalid Token Address');
    uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
    IERC20(_token).safeTransfer(_to, _contractBalance);
  }
}


exec
/bin/bash -lc "nl -ba src/FrontrunMarket.sol | sed -n '1,260p'" in /tmp/codex-dgrhxd3v
 succeeded in 0ms:
     1	//SPDX-License-Identifier: Unlicense
     2	pragma solidity 0.8.20;
     3	
     4	import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';
     5	import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
     6	import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
     7	import '@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol';
     8	import '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
     9	import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
    10	import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
    11	import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
    12	
    13	struct Token {
    14	  address token;
    15	  uint48 settleTime;
    16	  uint48 settleDuration;
    17	  uint152 settleRate; // number of token per point
    18	  uint8 status; //
    19	  uint256 minAmount;
    20	}
    21	
    22	struct Offer {
    23	  uint8 offerType;
    24	  bytes32 tokenId;
    25	  address exToken;
    26	  uint256 amount;
    27	  uint256 value;
    28	  uint256 collateral;
    29	  uint256 filledAmount;
    30	  uint8 status;
    31	  address offeredBy;
    32	  uint256 minAmount;
    33	}
    34	
    35	struct Order {
    36	  uint256 offerId;
    37	  uint256 amount;
    38	  address seller;
    39	  address buyer;
    40	  uint8 status;
    41	}
    42	
    43	struct Config {
    44	  uint256 pledgeRate;
    45	  uint256 feeRefund;
    46	  uint256 feeSettle;
    47	  address feeWallet;
    48	}
    49	
    50	contract FrontrunMarket is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable {
    51	  using SafeERC20 for IERC20;
    52	
    53	  uint256 constant WEI6 = 10 ** 6;
    54	  uint8 constant OFFER_BUY = 1;
    55	  uint8 constant OFFER_SELL = 2;
    56	
    57	  // Status
    58	  // Offer status
    59	  uint8 constant STATUS_OFFER_OPEN = 1;
    60	  uint8 constant STATUS_OFFER_FILLED = 2;
    61	  uint8 constant STATUS_OFFER_CANCELLED = 3;
    62	
    63	  // Order Status
    64	  uint8 constant STATUS_ORDER_OPEN = 1;
    65	  uint8 constant STATUS_ORDER_SETTLE_FILLED = 2;
    66	  uint8 constant STATUS_ORDER_SETTLE_CANCELLED = 3;
    67	  uint8 constant STATUS_ORDER_CANCELLED = 4;
    68	
    69	  // token status
    70	  uint8 constant STATUS_TOKEN_ACTIVE = 1;
    71	  uint8 constant STATUS_TOKEN_INACTIVE = 2;
    72	  uint8 constant STATUS_TOKEN_SETTLE = 3;
    73	
    74	  struct MarketStorage {
    75	    mapping(address => bool) acceptedTokens;
    76	    mapping(bytes32 => Token) tokens;
    77	    mapping(uint256 => Offer) offers;
    78	    uint256 lastOfferId;
    79	    mapping(uint256 => Order) orders;
    80	    uint256 lastOrderId;
    81	    Config config;
    82	  }
    83	
    84	  //keccak256(abi.encode(uint256(keccak256("frontrun.Market")) - 1)) & ~bytes32(uint256(0xff));
    85	  bytes32 constant MarketStorageLocation = 0x02c326031f2f3dedd13e7fde59c7c20af05a8826f4c07e8d42538c5f2be06800;
    86	
    87	  function _getOwnStorage() private pure returns (MarketStorage storage $) {
    88	    assembly {
    89	      $.slot := MarketStorageLocation
    90	    }
    91	  }
    92	
    93	  // event
    94	
    95	  event NewOffer(
    96	    uint256 id,
    97	    uint8 offerType,
    98	    bytes32 tokenId,
    99	    address exToken,
   100	    uint256 amount,
   101	    uint256 value,
   102	    uint256 collateral,
   103	    uint256 minAmount,
   104	    address doer
   105	  );
   106	  event NewToken(bytes32 tokenId, uint256 settleDuration, uint256 minAmount);
   107	  event NewOrder(uint256 id, uint256 offerId, uint256 amount, address seller, address buyer);
   108	
   109	  event SettleFilled(uint256 orderId, uint256 value, uint256 fee, address doer);
   110	  event SettleCancelled(uint256 orderId, uint256 value, uint256 fee, address doer);
   111	
   112	  event CancelOrder(uint256 orderId, address doer);
   113	  event CancelOffer(uint256 offerId, uint256 refundValue, uint256 refundFee, address doer);
   114	
   115	  event UpdateAcceptedTokens(address[] tokens, bool isAccepted);
   116	
   117	  event CloseOffer(uint256 offerId, uint256 refundAmount);
   118	
   119	  event UpdateConfig(
   120	    address oldFeeWallet,
   121	    uint256 oldFeeSettle,
   122	    uint256 oldFeeRefund,
   123	    uint256 oldPledgeRate,
   124	    address newFeeWallet,
   125	    uint256 newFeeSettle,
   126	    uint256 newFeeRefund,
   127	    uint256 newPledgeRate
   128	  );
   129	
   130	  event TokenToSettlePhase(bytes32 tokenId, address token, uint256 settleRate, uint256 settleTime);
   131	  event UpdateTokenStatus(bytes32 tokenId, uint8 oldValue, uint8 newValue);
   132	  event TokenForceCancelSettlePhase(bytes32 tokenId);
   133	
   134	  event Settle2Steps(uint256 orderId, bytes32 hash, address doer);
   135	
   136	  event UpdateTokenSettleDuration(bytes32 tokenId, uint48 oldValue, uint48 newValue);
   137	
   138	  function initialize() external initializer {
   139	    __Ownable_init(msg.sender);
   140	    // init value
   141	    MarketStorage storage $ = _getOwnStorage();
   142	    $.config.pledgeRate = WEI6; //
   143	    $.config.feeWallet = owner();
   144	    $.config.feeSettle = WEI6 / 200; // 0.5%
   145	    $.config.feeRefund = 0; // 0.0%
   146	  }
   147	
   148	  ///////////////////////////
   149	  ////// SYSTEM ACTION //////
   150	  ///////////////////////////
   151	
   152	  /// @notice Creates a market to trade
   153	  /// @param tokenId market identifier
   154	  /// @param settleDuration Duration after which buyer of token can claim collateral if seller hasnt delivered
   155	  /// @param minAmount minAmount that can be filled for this particular market including offers and orders , 6 decimals
   156	  // add min order value in market so that in settle2steps we dont have to pay gas to settle low value orders
   157	  function createToken(bytes32 tokenId, uint48 settleDuration, uint256 minAmount) external onlyOwner {
   158	    MarketStorage storage $ = _getOwnStorage();
   159	    Token storage _token = $.tokens[tokenId];
   160	    _token.settleDuration = settleDuration;
   161	    _token.minAmount = minAmount;
   162	    _token.status = STATUS_TOKEN_ACTIVE;
   163	    emit NewToken(tokenId, settleDuration, minAmount);
   164	  }
   165	
   166	  /// @notice moves a market to settlement phase
   167	  /// @param tokenId market identifier
   168	  /// @param tokenAddress contract address for token to be delivered
   169	  /// @param settleRate how many tokens per 1M amount traded(if token to be delivered has 18 decimals write 1e12)
   170	  function tokenToSettlePhase(
   171	    bytes32 tokenId,
   172	    address tokenAddress,
   173	    uint152 settleRate // how many token for 1M points
   174	  ) external onlyOwner {
   175	    MarketStorage storage $ = _getOwnStorage();
   176	    Token storage _token = $.tokens[tokenId];
   177	    require(tokenAddress != address(0), 'Invalid Token Address');
   178	    require(settleRate > 0, 'Invalid Settle Rate');
   179	    require(_token.status == STATUS_TOKEN_ACTIVE || _token.status == STATUS_TOKEN_INACTIVE, 'Invalid Token Status');
   180	    _token.token = tokenAddress;
   181	    _token.settleRate = settleRate;
   182	    // update token settle status & time
   183	    _token.status = STATUS_TOKEN_SETTLE;
   184	    _token.settleTime = uint48(block.timestamp);
   185	
   186	    emit TokenToSettlePhase(tokenId, tokenAddress, settleRate, block.timestamp);
   187	  }
   188	
   189	  /// @notice toggle market active inactive
   190	  /// @param tokenId market identifier
   191	  function tokenToggleActivation(bytes32 tokenId) external onlyOwner {
   192	    MarketStorage storage $ = _getOwnStorage();
   193	    Token storage _token = $.tokens[tokenId];
   194	    uint8 fromStatus = _token.status;
   195	    uint8 toStatus = fromStatus == STATUS_TOKEN_ACTIVE ? STATUS_TOKEN_INACTIVE : STATUS_TOKEN_ACTIVE;
   196	
   197	    require(fromStatus == STATUS_TOKEN_ACTIVE || fromStatus == STATUS_TOKEN_INACTIVE, 'Cannot Change Token Status');
   198	
   199	    _token.status = toStatus;
   200	    emit UpdateTokenStatus(tokenId, fromStatus, toStatus);
   201	  }
   202	
   203	  /// @notice  // in case wrong setting for settle
   204	  /// @param tokenId market identifier
   205	  function tokenForceCancelSettlePhase(bytes32 tokenId) external onlyOwner {
   206	    MarketStorage storage $ = _getOwnStorage();
   207	    Token storage _token = $.tokens[tokenId];
   208	    require(_token.status == STATUS_TOKEN_SETTLE, 'Invalid Token Status');
   209	    _token.status = STATUS_TOKEN_INACTIVE;
   210	    emit TokenForceCancelSettlePhase(tokenId);
   211	  }
   212	  /// @notice  // updates settleDuration
   213	  /// @param tokenId market identifier
   214	  /// @param newValue settleDuration value
   215	
   216	  function updateSettleDuration(bytes32 tokenId, uint48 newValue) external onlyOwner {
   217	    MarketStorage storage $ = _getOwnStorage();
   218	    Token storage _token = $.tokens[tokenId];
   219	    uint48 oldValue = _token.settleDuration;
   220	    _token.settleDuration = newValue;
   221	    emit UpdateTokenSettleDuration(tokenId, oldValue, newValue);
   222	  }
   223	
   224	  // force cancel order - by Operator/Owner
   225	  // refund for both seller & buyer without any fees cut
   226	  /// @notice
   227	  /// @param orderId order identifier
   228	  function forceCancelOrder(uint256 orderId) public nonReentrant onlyOwner {
   229	    MarketStorage storage $ = _getOwnStorage();
   230	    Order storage order = $.orders[orderId];
   231	    Offer storage offer = $.offers[order.offerId];
   232	
   233	    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');
   234	
   235	    // calculate refund
   236	    uint256 buyerRefundValue = (order.amount * offer.value) / offer.amount; // value
   237	    uint256 sellerRefundValue = (order.amount * offer.collateral) / offer.amount; // collateral
   238	    address buyer = order.buyer;
   239	    address seller = order.seller;
   240	
   241	    // refund
   242	    if (offer.exToken == address(0)) {
   243	      // refund ETH
   244	      if (buyerRefundValue > 0 && buyer != address(0)) {
   245	        (bool success,) = buyer.call{value: buyerRefundValue}('');
   246	        require(success, 'Transfer Funds to Seller Fail');
   247	      }
   248	      if (sellerRefundValue > 0 && seller != address(0)) {
   249	        (bool success,) = seller.call{value: sellerRefundValue}('');
   250	        require(success, 'Transfer Funds to Seller Fail');
   251	      }
   252	    } else {
   253	      IERC20 iexToken = IERC20(offer.exToken);
   254	      if (buyerRefundValue > 0 && buyer != address(0)) {
   255	        iexToken.safeTransfer(buyer, buyerRefundValue);
   256	      }
   257	      if (sellerRefundValue > 0 && seller != address(0)) {
   258	        iexToken.safeTransfer(seller, sellerRefundValue);
   259	      }
   260	    }

exec
/bin/bash -lc "nl -ba src/FrontrunMarket.sol | sed -n '261,520p'" in /tmp/codex-dgrhxd3v
 succeeded in 0ms:
   261	    order.status = STATUS_ORDER_CANCELLED;
   262	    emit CancelOrder(orderId, msg.sender);
   263	  }
   264	
   265	  // 2 steps settle:
   266	  // Tx1: Seller sending token to system vault/buyer
   267	  // Tx2: then Operator/Owner verify and settle to pay seller money+collateral
   268	  function settle2Steps(uint256 orderId, bytes32 hash) public nonReentrant onlyOwner {
   269	    MarketStorage storage $ = _getOwnStorage();
   270	    Order storage order = $.orders[orderId];
   271	    Offer storage offer = $.offers[order.offerId];
   272	    Token storage token = $.tokens[offer.tokenId];
   273	
   274	    // check condition
   275	    require(token.status == STATUS_TOKEN_SETTLE, 'Invalid Status');
   276	    require(token.token != address(0) && token.settleRate > 0, 'Token Not Set');
   277	    require(block.timestamp > token.settleTime, 'Settling Time Not Started');
   278	    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');
   279	
   280	    uint256 collateral = (order.amount * offer.collateral) / offer.amount;
   281	    uint256 value = (order.amount * offer.value) / offer.amount;
   282	
   283	    // transfer liquid to seller
   284	    uint256 settleFee = (value * $.config.feeSettle) / WEI6;
   285	    uint256 totalValue = value + collateral - settleFee;
   286	    if (offer.exToken == address(0)) {
   287	      // by ETH
   288	      (bool success1,) = order.seller.call{value: totalValue}('');
   289	      (bool success2,) = $.config.feeWallet.call{value: settleFee}('');
   290	      require(success1 && success2, 'Transfer Funds Fail');
   291	    } else {
   292	      // by exToken
   293	      IERC20 iexToken = IERC20(offer.exToken);
   294	      iexToken.safeTransfer(order.seller, totalValue);
   295	      iexToken.safeTransfer($.config.feeWallet, settleFee);
   296	    }
   297	
   298	    order.status = STATUS_ORDER_SETTLE_FILLED;
   299	
   300	    emit Settle2Steps(orderId, hash, msg.sender);
   301	    emit SettleFilled(orderId, totalValue, settleFee, msg.sender);
   302	  }
   303	
   304	  function settle2StepsBatch(uint256[] calldata orderIds, bytes32[] calldata hashes) external {
   305	    require(orderIds.length == hashes.length, 'Invalid Input');
   306	    for (uint256 i = 0; i < orderIds.length; i++) {
   307	      settle2Steps(orderIds[i], hashes[i]);
   308	    }
   309	  }
   310	
   311	  /////////////////////////
   312	  ////// USER ACTION //////
   313	  /////////////////////////
   314	
   315	  // make a offer request using erc20 collateral
   316	  /// @notice
   317	  /// @param tokenId market identifier
   318	  /// @param amount amount of market asset in 6 decimals
   319	  /// @param value amount of collateral being deposited, price = collateral/amount
   320	  /// @param exToken collateral token contract addres
   321	  /// @param minAmount min amount that must be filled by the counterparty in 6 decimals
   322	  function newOffer(
   323	    uint8 offerType,
   324	    bytes32 tokenId,
   325	    uint256 amount, //amount of asset
   326	    uint256 value, // amount of collateral
   327	    address exToken,
   328	    uint256 minAmount
   329	  ) external nonReentrant {
   330	    MarketStorage storage $ = _getOwnStorage();
   331	    Token storage token = $.tokens[tokenId];
   332	    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid Token');
   333	    require(exToken != address(0) && $.acceptedTokens[exToken], 'Invalid Offer Token');
   334	    require(amount > 0 && value > 0, 'Invalid Amount or Value');
   335	    require(amount >= minAmount, 'minamount to be filled cant be greater then amount');
   336	    require(minAmount >= token.minAmount, 'minamount should be greater then eual to market global minamount');
   337	    IERC20 iexToken = IERC20(exToken);
   338	    // collateral
   339	    uint256 collateral = (value * $.config.pledgeRate) / WEI6;
   340	
   341	    // transfer offer value (offer buy) or collateral (offer sell)
   342	    uint256 _transferAmount = offerType == OFFER_BUY ? value : collateral;
   343	    iexToken.safeTransferFrom(msg.sender, address(this), _transferAmount);
   344	
   345	    // create new offer
   346	    _newOffer(offerType, tokenId, exToken, amount, value, collateral, minAmount);
   347	  }
   348	
   349	  // New offer in ETH
   350	  /// @notice
   351	  /// @param tokenId market identifier
   352	  /// @param amount amount of market asset in 6 decimals
   353	  /// @param value amount of collateral being deposited, price = collateral/amount
   354	  /// @param minAmount min amount that must be filled by the counterparty in 6 decimals
   355	  function newOfferETH(
   356	    uint8 offerType,
   357	    bytes32 tokenId,
   358	    uint256 amount,
   359	    uint256 value,
   360	    uint256 minAmount
   361	  ) external payable nonReentrant {
   362	    MarketStorage storage $ = _getOwnStorage();
   363	    Token storage token = $.tokens[tokenId];
   364	    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid Token');
   365	    require(amount > 0 && value > 0, 'Invalid Amount or Value');
   366	    require(amount >= minAmount, 'minamount to be filled cant be greater then amount');
   367	    require(minAmount >= token.minAmount, 'minamount should be greater then eual to market global minamount');
   368	
   369	    // collateral
   370	    uint256 collateral = (value * $.config.pledgeRate) / WEI6;
   371	
   372	    uint256 _ethAmount = offerType == OFFER_BUY ? value : collateral;
   373	    require(_ethAmount == msg.value, 'Insufficient Funds');
   374	    // create new offer
   375	    _newOffer(offerType, tokenId, address(0), amount, value, collateral, minAmount);
   376	  }
   377	
   378	  /// @notice fill an offer with erc20 collateral and generate an orderid associated with a trade
   379	  /// @param offerId offer identifier
   380	  /// @param amount amount of market asset in 6 decimals
   381	  function fillOffer(uint256 offerId, uint256 amount) public {
   382	    MarketStorage storage $ = _getOwnStorage();
   383	    Offer storage offer = $.offers[offerId];
   384	    Token storage token = $.tokens[offer.tokenId];
   385	    require(offer.status == STATUS_OFFER_OPEN, 'Invalid Offer Status');
   386	    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid token Status');
   387	    require(amount > 0, 'Invalid Amount');
   388	    require(offer.amount - offer.filledAmount >= amount, 'Insufficient Allocations');
   389	    if (offer.amount - offer.filledAmount > offer.minAmount) {
   390	      require(amount >= offer.minAmount, 'cannot fill less then offer minamount');
   391	    } else {
   392	      require(amount == offer.amount - offer.filledAmount, 'if less then asset minamount must fill complete order');
   393	    }
   394	    require(offer.exToken != address(0), 'Invalid Offer Token');
   395	
   396	    // transfer value or collecteral
   397	    IERC20 iexToken = IERC20(offer.exToken);
   398	    uint256 _transferAmount;
   399	    address buyer;
   400	    address seller;
   401	    if (offer.offerType == OFFER_BUY) {
   402	      _transferAmount = (offer.collateral * amount) / offer.amount;
   403	      buyer = offer.offeredBy;
   404	      seller = msg.sender;
   405	    } else {
   406	      _transferAmount = (offer.value * amount) / offer.amount;
   407	      buyer = msg.sender;
   408	      seller = offer.offeredBy;
   409	    }
   410	    iexToken.safeTransferFrom(msg.sender, address(this), _transferAmount);
   411	
   412	    _fillOffer(offerId, amount, buyer, seller);
   413	  }
   414	
   415	  /// @notice fill an offer with eth collateral and generate an orderid associated with a trade
   416	  /// @param offerId offer identifier
   417	  /// @param amount amount of market asset in 6 decimals
   418	  function fillOfferETH(uint256 offerId, uint256 amount) external payable nonReentrant {
   419	    uint256 _ethAmount = _fillOfferETH(offerId, amount);
   420	    require(msg.value >= _ethAmount, 'Insufficient Funds');
   421	    require(_ethAmount + 5 * 1e16 > msg.value, 'Extra Amount Sent');
   422	  }
   423	
   424	  /// @notice batche fill both types of offers
   425	  function batchFillOffer(uint256[] calldata offerId, uint256[] calldata amount) external payable nonReentrant {
   426	    require(offerId.length == amount.length, 'Invalid Input');
   427	    uint256 _ethAmount;
   428	    MarketStorage storage $ = _getOwnStorage();
   429	    for (uint256 i = 0; i < offerId.length; i++) {
   430	      Offer storage offer = $.offers[offerId[i]];
   431	      if (offer.exToken == address(0)) {
   432	        _ethAmount += _fillOfferETH(offerId[i], amount[i]);
   433	      } else {
   434	        fillOffer(offerId[i], amount[i]);
   435	      }
   436	    }
   437	    require(msg.value >= _ethAmount, 'Insufficient Funds');
   438	    require(_ethAmount + 5 * 1e16 > msg.value, 'Extra Amount Sent');
   439	  }
   440	
   441	  function _fillOfferETH(uint256 offerId, uint256 amount) internal returns (uint256 _ethAmount) {
   442	    MarketStorage storage $ = _getOwnStorage();
   443	    Offer storage offer = $.offers[offerId];
   444	    Token storage token = $.tokens[offer.tokenId];
   445	    require(offer.status == STATUS_OFFER_OPEN, 'Invalid Offer Status');
   446	    require(token.status == STATUS_TOKEN_ACTIVE, 'Invalid token Status');
   447	    require(amount > 0, 'Invalid Amount');
   448	    require(offer.amount - offer.filledAmount >= amount, 'Insufficient Allocations');
   449	    if (offer.amount - offer.filledAmount > offer.minAmount) {
   450	      require(amount >= offer.minAmount, 'cannot fill less then offer minamount or asset minamount');
   451	    } else {
   452	      require(amount == offer.amount - offer.filledAmount, 'if less then asset minamount must fill complete order');
   453	    }
   454	    require(offer.exToken == address(0), 'Invalid Offer Token');
   455	
   456	    // transfer value or collecteral
   457	    address buyer;
   458	    address seller;
   459	    if (offer.offerType == OFFER_BUY) {
   460	      _ethAmount = (offer.collateral * amount) / offer.amount;
   461	      buyer = offer.offeredBy;
   462	      seller = msg.sender;
   463	    } else {
   464	      _ethAmount = (offer.value * amount) / offer.amount;
   465	      buyer = msg.sender;
   466	      seller = offer.offeredBy;
   467	    }
   468	    // new order
   469	    _fillOffer(offerId, amount, buyer, seller);
   470	  }
   471	
   472	  /// @notice close unfullfilled offer - by Offer owner
   473	  /// @param offerId offer identifier
   474	  function cancelOffer(uint256 offerId) public nonReentrant {
   475	    MarketStorage storage $ = _getOwnStorage();
   476	    Offer storage offer = $.offers[offerId];
   477	
   478	    require(offer.offeredBy == msg.sender, 'Offer Owner Only');
   479	    require(offer.status == STATUS_OFFER_OPEN, 'Invalid Offer Status');
   480	
   481	    uint256 refundAmount = offer.amount - offer.filledAmount;
   482	    require(refundAmount > 0, 'Insufficient Allocations');
   483	
   484	    // calculate refund
   485	    uint256 refundValue;
   486	    if (offer.offerType == OFFER_BUY) {
   487	      refundValue = (refundAmount * offer.value) / offer.amount;
   488	    } else {
   489	      refundValue = (refundAmount * offer.collateral) / offer.amount;
   490	    }
   491	    uint256 refundFee = (refundValue * $.config.feeRefund) / WEI6;
   492	    refundValue -= refundFee;
   493	
   494	    // refund
   495	    if (offer.exToken == address(0)) {
   496	      // refund ETH
   497	      (bool success1,) = offer.offeredBy.call{value: refundValue}('');
   498	      (bool success2,) = $.config.feeWallet.call{value: refundFee}('');
   499	      require(success1 && success2, 'Transfer Funds Fail');
   500	    } else {
   501	      IERC20 iexToken = IERC20(offer.exToken);
   502	      iexToken.safeTransfer(offer.offeredBy, refundValue);
   503	      iexToken.safeTransfer($.config.feeWallet, refundFee);
   504	    }
   505	
   506	    offer.status = STATUS_OFFER_CANCELLED;
   507	    emit CancelOffer(offerId, refundValue, refundFee, msg.sender);
   508	  }
   509	
   510	  //
   511	  /// @notice settle order - deliver token to finillize the order
   512	  /// @param orderId order identifier
   513	  function settleFilled(uint256 orderId) public nonReentrant {
   514	    MarketStorage storage $ = _getOwnStorage();
   515	    Order storage order = $.orders[orderId];
   516	    Offer storage offer = $.offers[order.offerId];
   517	    Token storage token = $.tokens[offer.tokenId];
   518	
   519	    // check condition
   520	    require(token.status == STATUS_TOKEN_SETTLE, 'Invalid Status');

exec
/bin/bash -lc "nl -ba src/FrontrunMarket.sol | sed -n '521,760p'" in /tmp/codex-dgrhxd3v
 succeeded in 0ms:
   521	    require(token.token != address(0) && token.settleRate > 0, 'Token Not Set');
   522	    require(block.timestamp > token.settleTime, 'Settling Time Not Started');
   523	    require(order.seller == msg.sender, 'Seller Only');
   524	    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');
   525	
   526	    uint256 collateral = (order.amount * offer.collateral) / offer.amount;
   527	    uint256 value = (order.amount * offer.value) / offer.amount;
   528	
   529	    // transfer token to buyer
   530	    IERC20 iToken = IERC20(token.token);
   531	    // calculate token amount base on it's decimals
   532	    uint256 tokenAmount = (order.amount * token.settleRate) / WEI6;
   533	    uint256 tokenAmountFee = (tokenAmount * $.config.feeSettle) / WEI6;
   534	    // transfer order fee in token to fee wallet
   535	    iToken.safeTransferFrom(order.seller, $.config.feeWallet, tokenAmountFee);
   536	    // transfer token after fee to buyer
   537	    iToken.safeTransferFrom(order.seller, order.buyer, tokenAmount - tokenAmountFee);
   538	
   539	    // transfer liquid to seller
   540	    uint256 settleFee = (value * $.config.feeSettle) / WEI6;
   541	    uint256 totalValue = value + collateral - settleFee;
   542	    if (offer.exToken == address(0)) {
   543	      // by ETH
   544	      (bool success1,) = order.seller.call{value: totalValue}('');
   545	      (bool success2,) = $.config.feeWallet.call{value: settleFee}('');
   546	      require(success1 && success2, 'Transfer Funds Fail');
   547	    } else {
   548	      // by exToken
   549	      IERC20 iexToken = IERC20(offer.exToken);
   550	      iexToken.safeTransfer(order.seller, totalValue);
   551	      iexToken.safeTransfer($.config.feeWallet, settleFee);
   552	    }
   553	
   554	    order.status = STATUS_ORDER_SETTLE_FILLED;
   555	
   556	    emit SettleFilled(orderId, totalValue, settleFee, msg.sender);
   557	  }
   558	
   559	  /// @notice cancel unfilled order by token buyer after settleDuration time frame
   560	  /// @notice   // token seller lose collateral to token buyer
   561	  /// @param orderId order identifier
   562	
   563	  function settleCancelled(uint256 orderId) public nonReentrant {
   564	    MarketStorage storage $ = _getOwnStorage();
   565	    Order storage order = $.orders[orderId];
   566	    Offer storage offer = $.offers[order.offerId];
   567	    Token storage token = $.tokens[offer.tokenId];
   568	
   569	    // check condition
   570	    require(token.status == STATUS_TOKEN_SETTLE, 'Invalid Status');
   571	    require(block.timestamp > token.settleTime + token.settleDuration, 'Settling Time Not Ended Yet');
   572	    require(order.status == STATUS_ORDER_OPEN, 'Invalid Order Status');
   573	    require(order.buyer == msg.sender || owner() == msg.sender, 'Buyer or Operator/Owner Only');
   574	
   575	    uint256 collateral = (order.amount * offer.collateral) / offer.amount;
   576	    uint256 value = (order.amount * offer.value) / offer.amount;
   577	
   578	    // transfer liquid to buyer
   579	    uint256 settleFee = (collateral * $.config.feeSettle * 2) / WEI6;
   580	    uint256 totalValue = value + collateral - settleFee;
   581	    if (offer.exToken == address(0)) {
   582	      // by ETH
   583	      (bool success1,) = order.buyer.call{value: totalValue}('');
   584	      (bool success2,) = $.config.feeWallet.call{value: settleFee}('');
   585	      require(success1 && success2, 'Transfer Funds Fail');
   586	    } else {
   587	      // by exToken
   588	      IERC20 iexToken = IERC20(offer.exToken);
   589	      iexToken.safeTransfer(order.buyer, totalValue);
   590	      iexToken.safeTransfer($.config.feeWallet, settleFee);
   591	    }
   592	
   593	    order.status = STATUS_ORDER_SETTLE_CANCELLED;
   594	
   595	    emit SettleCancelled(orderId, totalValue, settleFee, msg.sender);
   596	  }
   597	
   598	  // Batch actions
   599	  function batchForceCancelOrders(uint256[] calldata orderIds) external {
   600	    for (uint256 i = 0; i < orderIds.length; i++) {
   601	      forceCancelOrder(orderIds[i]);
   602	    }
   603	  }
   604	
   605	  function batchCancelOffers(uint256[] calldata offerIds) external {
   606	    for (uint256 i = 0; i < offerIds.length; i++) {
   607	      cancelOffer(offerIds[i]);
   608	    }
   609	  }
   610	
   611	  function batchSettleFilleds(uint256[] calldata orderIds) external {
   612	    for (uint256 i = 0; i < orderIds.length; i++) {
   613	      settleFilled(orderIds[i]);
   614	    }
   615	  }
   616	
   617	  function batchSettleCancelleds(uint256[] calldata orderIds) external {
   618	    for (uint256 i = 0; i < orderIds.length; i++) {
   619	      settleCancelled(orderIds[i]);
   620	    }
   621	  }
   622	
   623	  ///////////////////////////
   624	  ///////// SETTER //////////
   625	  ///////////////////////////
   626	
   627	  function updateConfig(
   628	    address feeWallet_,
   629	    uint256 feeSettle_,
   630	    uint256 feeRefund_,
   631	    uint256 pledgeRate_
   632	  ) external onlyOwner {
   633	    MarketStorage storage $ = _getOwnStorage();
   634	    require(feeWallet_ != address(0), 'Invalid Address');
   635	    require(feeSettle_ <= WEI6 / 100, 'Settle Fee <= 10%');
   636	    require(feeRefund_ <= WEI6 / 100, 'Cancel Fee <= 10%');
   637	    emit UpdateConfig(
   638	      $.config.feeWallet,
   639	      $.config.feeSettle,
   640	      $.config.feeRefund,
   641	      $.config.pledgeRate,
   642	      feeWallet_,
   643	      feeSettle_,
   644	      feeRefund_,
   645	      pledgeRate_
   646	    );
   647	    // update
   648	    $.config.feeWallet = feeWallet_;
   649	    $.config.feeSettle = feeSettle_;
   650	    $.config.feeRefund = feeRefund_;
   651	    $.config.pledgeRate = pledgeRate_;
   652	  }
   653	
   654	  //
   655	  /// @notice adding/removing asset to be allowed to use as collateral
   656	  function setAcceptedTokens(address[] calldata tokenAddresses, bool isAccepted) external onlyOwner {
   657	    MarketStorage storage $ = _getOwnStorage();
   658	
   659	    for (uint256 i = 0; i < tokenAddresses.length; i++) {
   660	      $.acceptedTokens[tokenAddresses[i]] = isAccepted;
   661	    }
   662	    emit UpdateAcceptedTokens(tokenAddresses, isAccepted);
   663	  }
   664	
   665	  ///////////////////////////
   666	  ///////// GETTER //////////
   667	  ///////////////////////////
   668	  function offerAmount(uint256 offerId) external view returns (uint256) {
   669	    MarketStorage storage $ = _getOwnStorage();
   670	    return $.offers[offerId].amount;
   671	  }
   672	
   673	  function offerAmountAvailable(uint256 offerId) external view returns (uint256) {
   674	    MarketStorage storage $ = _getOwnStorage();
   675	    return $.offers[offerId].amount - $.offers[offerId].filledAmount;
   676	  }
   677	
   678	  function offerValue(uint256 offerId) external view returns (uint256) {
   679	    MarketStorage storage $ = _getOwnStorage();
   680	    return $.offers[offerId].value;
   681	  }
   682	
   683	  function offerExToken(uint256 offerId) external view returns (address) {
   684	    MarketStorage storage $ = _getOwnStorage();
   685	    return $.offers[offerId].exToken;
   686	  }
   687	
   688	  function isBuyOffer(uint256 offerId) external view returns (bool) {
   689	    MarketStorage storage $ = _getOwnStorage();
   690	    return $.offers[offerId].offerType == OFFER_BUY;
   691	  }
   692	
   693	  function isSellOffer(uint256 offerId) external view returns (bool) {
   694	    MarketStorage storage $ = _getOwnStorage();
   695	    return $.offers[offerId].offerType == OFFER_SELL;
   696	  }
   697	
   698	  function offerStatus(uint256 offerId) external view returns (uint256) {
   699	    MarketStorage storage $ = _getOwnStorage();
   700	    return $.offers[offerId].status;
   701	  }
   702	
   703	  function orderStatus(uint256 orderId) external view returns (uint256) {
   704	    MarketStorage storage $ = _getOwnStorage();
   705	    return $.orders[orderId].status;
   706	  }
   707	
   708	  function tokens(bytes32 tokenId) external view returns (Token memory) {
   709	    MarketStorage storage $ = _getOwnStorage();
   710	    return $.tokens[tokenId];
   711	  }
   712	
   713	  function offers(uint256 id) external view returns (Offer memory) {
   714	    MarketStorage storage $ = _getOwnStorage();
   715	    return $.offers[id];
   716	  }
   717	
   718	  function orders(uint256 id) external view returns (Order memory) {
   719	    MarketStorage storage $ = _getOwnStorage();
   720	    return $.orders[id];
   721	  }
   722	
   723	  function config() external view returns (Config memory) {
   724	    MarketStorage storage $ = _getOwnStorage();
   725	    return $.config;
   726	  }
   727	
   728	  function isAcceptedToken(address token) external view returns (bool) {
   729	    MarketStorage storage $ = _getOwnStorage();
   730	    return $.acceptedTokens[token];
   731	  }
   732	
   733	  function lastOfferId() external view returns (uint256) {
   734	    MarketStorage storage $ = _getOwnStorage();
   735	    return $.lastOfferId;
   736	  }
   737	
   738	  function lastOrderId() external view returns (uint256) {
   739	    MarketStorage storage $ = _getOwnStorage();
   740	    return $.lastOrderId;
   741	  }
   742	
   743	  ///////////////////////////
   744	  //////// INTERNAL /////////
   745	  ///////////////////////////
   746	  function _newOffer(
   747	    uint8 offerType,
   748	    bytes32 tokenId,
   749	    address exToken,
   750	    uint256 amount,
   751	    uint256 value,
   752	    uint256 collateral,
   753	    uint256 minAmount
   754	  ) internal {
   755	    MarketStorage storage $ = _getOwnStorage();
   756	    // create new offer
   757	    $.offers[++$.lastOfferId] =
   758	      Offer(offerType, tokenId, exToken, amount, value, collateral, 0, STATUS_OFFER_OPEN, msg.sender, minAmount);
   759	
   760	    emit NewOffer($.lastOfferId, offerType, tokenId, exToken, amount, value, collateral, minAmount, msg.sender);

exec
/bin/bash -lc "nl -ba src/FrontrunMarket.sol | sed -n '760,820p'" in /tmp/codex-dgrhxd3v
 succeeded in 0ms:
   760	    emit NewOffer($.lastOfferId, offerType, tokenId, exToken, amount, value, collateral, minAmount, msg.sender);
   761	  }
   762	
   763	  function _fillOffer(uint256 offerId, uint256 amount, address buyer, address seller) internal {
   764	    MarketStorage storage $ = _getOwnStorage();
   765	    Offer storage offer = $.offers[offerId];
   766	    // new order
   767	    $.orders[++$.lastOrderId] = Order(offerId, amount, seller, buyer, STATUS_ORDER_OPEN);
   768	
   769	    // check if offer is fullfilled
   770	    offer.filledAmount += amount;
   771	    if (offer.filledAmount == offer.amount) {
   772	      offer.status = STATUS_OFFER_FILLED;
   773	      emit CloseOffer(offerId, 0);
   774	    }
   775	
   776	    emit NewOrder($.lastOrderId, offerId, amount, seller, buyer);
   777	  }
   778	
   779	  // get stuck token in contract
   780	  function withdrawStuckToken(address _token, address _to) external onlyOwner {
   781	    MarketStorage storage $ = _getOwnStorage();
   782	    require(_token != address(0) && !$.acceptedTokens[_token], 'Invalid Token Address');
   783	    uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
   784	    IERC20(_token).safeTransfer(_to, _contractBalance);
   785	  }
   786	}

codex
{"no_findings":false,"summary":"Found three meaningful issues in the implementation: `fillOffer` is reentrant before state is updated, ERC20 collateral is accounted at nominal rather than received amounts, and the owner can de-whitelist then withdraw live escrowed tokens.","findings":[{"severity":"high","category":"reentrancy","title":"`fillOffer` can be reentered before `filledAmount` is updated, allowing overfilled orders","exploit_scenario":"If a whitelisted collateral token invokes callbacks during `transferFrom` (for example an ERC777-style token or a callback-capable ERC20), an attacker can fill the same offer reentrantly from a contract wallet. Each nested call observes the old `offer.filledAmount`, so multiple full-sized orders are created against one offer. The attacker can then self-settle those duplicated orders and extract more `value + collateral` than was originally escrowed, draining pooled liquidity or leaving the market insolvent.","recommendation":"Make `fillOffer` non-reentrant, or expose only a guarded external entrypoint and keep the core logic internal. Update `offer.filledAmount`/status before any external token transfer, or otherwise re-check remaining capacity after the transfer returns.","evidence":"`fillOffer` performs `iexToken.safeTransferFrom(...)` before calling `_fillOffer(...)`. `_fillOffer` is the only place that increments `offer.filledAmount` and closes the offer.","locations":["src/FrontrunMarket.sol:381","src/FrontrunMarket.sol:410","src/FrontrunMarket.sol:412","src/FrontrunMarket.sol:763","src/FrontrunMarket.sol:770"]},{"severity":"high","category":"other","title":"Nominal ERC20 accounting makes fee-on-transfer collateral tokens insolvent","exploit_scenario":"If governance ever accepts a deflationary / fee-on-transfer token as collateral, the market records `value` and `collateral` using the requested amounts instead of the actual tokens received. An attacker can self-trade using that token so the contract receives less than it promises to pay back on cancel / settle. The shortfall is then paid from unrelated users' escrow if available, or later exits revert because the vault is underfunded.","recommendation":"Only allow plain ERC20 collateral tokens, or account using balance deltas before and after each transfer. Reject deposits where the actual received amount is smaller than the expected amount.","evidence":"Both `newOffer` and `fillOffer` transfer ERC20s and then keep using caller-supplied `value` / derived `collateral`; no post-transfer balance check exists before later payouts in `forceCancelOrder`, `settleFilled`, and `settleCancelled`.","locations":["src/FrontrunMarket.sol:322","src/FrontrunMarket.sol:343","src/FrontrunMarket.sol:381","src/FrontrunMarket.sol:410","src/FrontrunMarket.sol:526","src/FrontrunMarket.sol:575"]},{"severity":"medium","category":"access_control","title":"Owner can confiscate live escrow by de-whitelisting a token before `withdrawStuckToken`","exploit_scenario":"While users still have open offers or orders denominated in token `X`, the owner can call `setAcceptedTokens([X], false)` and immediately follow with `withdrawStuckToken(X, owner)`. Because the withdrawal gate only checks the current whitelist bit, the function transfers the entire balance of `X`, including funds backing active positions. Those positions then remain recorded on-chain but cannot be settled or cancelled successfully.","recommendation":"Track escrowed balances per collateral token and forbid withdrawing any amount backing live offers/orders. `withdrawStuckToken` should not rely only on the current accepted-token flag.","evidence":"`setAcceptedTokens` can flip any token's accepted status at any time, and `withdrawStuckToken` transfers the full contract balance whenever `acceptedTokens[_token]` is false.","locations":["src/FrontrunMarket.sol:656","src/FrontrunMarket.sol:660","src/FrontrunMarket.sol:780","src/FrontrunMarket.sol:782","src/FrontrunMarket.sol:784"]}]}
tokens used
53,207
Metadata
{
    "codex": {
        "ran_at": "2026-04-04T03:33:51.721265+00:00",
        "returncode": 0
    },
    "project": {
        "mode": "source",
        "file_count": 19,
        "import_count": 22,
        "first_party_contracts_count": 1
    },
    "summary": "Found three meaningful issues in the implementation: `fillOffer` is reentrant before state is updated, ERC20 collateral is accounted at nominal rather than received amounts, and the owner can de-whitelist then withdraw live escrowed tokens.",
    "raw_final": {
        "summary": "Found three meaningful issues in the implementation: `fillOffer` is reentrant before state is updated, ERC20 collateral is accounted at nominal rather than received amounts, and the owner can de-whitelist then withdraw live escrowed tokens.",
        "findings": [
            {
                "title": "`fillOffer` can be reentered before `filledAmount` is updated, allowing overfilled orders",
                "category": "reentrancy",
                "evidence": "`fillOffer` performs `iexToken.safeTransferFrom(...)` before calling `_fillOffer(...)`. `_fillOffer` is the only place that increments `offer.filledAmount` and closes the offer.",
                "severity": "high",
                "locations": [
                    "src/FrontrunMarket.sol:381",
                    "src/FrontrunMarket.sol:410",
                    "src/FrontrunMarket.sol:412",
                    "src/FrontrunMarket.sol:763",
                    "src/FrontrunMarket.sol:770"
                ],
                "recommendation": "Make `fillOffer` non-reentrant, or expose only a guarded external entrypoint and keep the core logic internal. Update `offer.filledAmount`/status before any external token transfer, or otherwise re-check remaining capacity after the transfer returns.",
                "exploit_scenario": "If a whitelisted collateral token invokes callbacks during `transferFrom` (for example an ERC777-style token or a callback-capable ERC20), an attacker can fill the same offer reentrantly from a contract wallet. Each nested call observes the old `offer.filledAmount`, so multiple full-sized orders are created against one offer. The attacker can then self-settle those duplicated orders and extract more `value + collateral` than was originally escrowed, draining pooled liquidity or leaving the market insolvent."
            },
            {
                "title": "Nominal ERC20 accounting makes fee-on-transfer collateral tokens insolvent",
                "category": "other",
                "evidence": "Both `newOffer` and `fillOffer` transfer ERC20s and then keep using caller-supplied `value` / derived `collateral`; no post-transfer balance check exists before later payouts in `forceCancelOrder`, `settleFilled`, and `settleCancelled`.",
                "severity": "high",
                "locations": [
                    "src/FrontrunMarket.sol:322",
                    "src/FrontrunMarket.sol:343",
                    "src/FrontrunMarket.sol:381",
                    "src/FrontrunMarket.sol:410",
                    "src/FrontrunMarket.sol:526",
                    "src/FrontrunMarket.sol:575"
                ],
                "recommendation": "Only allow plain ERC20 collateral tokens, or account using balance deltas before and after each transfer. Reject deposits where the actual received amount is smaller than the expected amount.",
                "exploit_scenario": "If governance ever accepts a deflationary / fee-on-transfer token as collateral, the market records `value` and `collateral` using the requested amounts instead of the actual tokens received. An attacker can self-trade using that token so the contract receives less than it promises to pay back on cancel / settle. The shortfall is then paid from unrelated users' escrow if available, or later exits revert because the vault is underfunded."
            },
            {
                "title": "Owner can confiscate live escrow by de-whitelisting a token before `withdrawStuckToken`",
                "category": "access_control",
                "evidence": "`setAcceptedTokens` can flip any token's accepted status at any time, and `withdrawStuckToken` transfers the full contract balance whenever `acceptedTokens[_token]` is false.",
                "severity": "medium",
                "locations": [
                    "src/FrontrunMarket.sol:656",
                    "src/FrontrunMarket.sol:660",
                    "src/FrontrunMarket.sol:780",
                    "src/FrontrunMarket.sol:782",
                    "src/FrontrunMarket.sol:784"
                ],
                "recommendation": "Track escrowed balances per collateral token and forbid withdrawing any amount backing live offers/orders. `withdrawStuckToken` should not rely only on the current accepted-token flag.",
                "exploit_scenario": "While users still have open offers or orders denominated in token `X`, the owner can call `setAcceptedTokens([X], false)` and immediately follow with `withdrawStuckToken(X, owner)`. Because the withdrawal gate only checks the current whitelist bit, the function transfers the entire balance of `X`, including funds backing active positions. Those positions then remain recorded on-chain but cannot be settled or cancelled successfully."
            }
        ],
        "no_findings": false
    },
    "no_findings": false,
    "proxy_context": {
        "proxy_run_id": "019d5666-7204-7199-9428-87c933f7acea",
        "proxy_address": "0x849f4081899305a1fd24aac84db5174eb60dc28e",
        "implementation_run_id": "019d5666-ddb2-7138-a85b-4026ca2b3eec",
        "implementation_address": "0x6ac64c4760e0590f88233b2046810e87e0354324"
    },
    "schema_version": 1
}

Opcode Heuristics

tool.cast_disasm
Delegatecall
2
Selfdestruct
0
CREATE2
1
CALL-family (heavy)
0
EXT*/BALANCE
5
Total opcodes
762
Flags
delegatecall_present create_opcodes_present heavy_ext_balance_usage
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 0x000000000000000000000000735a07f45ad51548059f6d0a4756a0438bd871c8
00000030: PUSH1 0x01
00000032: PUSH1 0x01
00000034: PUSH1 0xa0
00000036: SHL
00000037: SUB
00000038: AND
00000039: CALLER
0000003a: SUB
0000003b: PUSH2 0x007b
0000003e: JUMPI
0000003f: PUSH1 0x00
00000041: CALLDATALOAD
00000042: PUSH1 0x01
00000044: PUSH1 0x01
00000046: PUSH1 0xe0
00000048: SHL
00000049: SUB
0000004a: NOT
0000004b: AND
0000004c: PUSH4 0x278f7943
00000051: PUSH1 0xe1
00000053: SHL
00000054: EQ
00000055: PUSH2 0x0071
00000058: JUMPI
00000059: PUSH1 0x40
0000005b: MLOAD
0000005c: PUSH4 0x34ad5dbb
00000061: PUSH1 0xe2
00000063: SHL
00000064: DUP2
00000065: MSTORE
00000066: PUSH1 0x04
00000068: ADD
00000069: PUSH1 0x40
0000006b: MLOAD
0000006c: DUP1
0000006d: SWAP2
0000006e: SUB
0000006f: SWAP1
00000070: REVERT
00000071: JUMPDEST
00000072: PUSH2 0x0079
00000075: PUSH2 0x0083
00000078: JUMP
00000079: JUMPDEST
0000007a: JUMP
0000007b: JUMPDEST
0000007c: PUSH2 0x0079
0000007f: PUSH2 0x00b2
00000082: JUMP
00000083: JUMPDEST
00000084: PUSH1 0x00
00000086: DUP1
00000087: PUSH2 0x0093
0000008a: CALLDATASIZE
0000008b: PUSH1 0x04
0000008d: DUP2
0000008e: DUP5
0000008f: PUSH2 0x02e0
00000092: JUMP
00000093: JUMPDEST
00000094: DUP2
00000095: ADD
00000096: SWAP1
00000097: PUSH2 0x00a0
0000009a: SWAP2
0000009b: SWAP1
0000009c: PUSH2 0x0320
0000009f: JUMP
000000a0: JUMPDEST
000000a1: SWAP2
000000a2: POP
000000a3: SWAP2
000000a4: POP
000000a5: PUSH2 0x00ae
000000a8: DUP3
000000a9: DUP3
000000aa: PUSH2 0x00c2
000000ad: JUMP
000000ae: JUMPDEST
000000af: POP
000000b0: POP
000000b1: JUMP
000000b2: JUMPDEST
000000b3: PUSH2 0x0079
000000b6: PUSH2 0x00bd
000000b9: PUSH2 0x011d
000000bc: JUMP
000000bd: JUMPDEST
000000be: PUSH2 0x0143
000000c1: JUMP
000000c2: JUMPDEST
000000c3: PUSH2 0x00cb
000000c6: DUP3
000000c7: PUSH2 0x0167
000000ca: JUMP
000000cb: JUMPDEST
000000cc: PUSH1 0x40
000000ce: MLOAD
000000cf: PUSH1 0x01
000000d1: PUSH1 0x01
000000d3: PUSH1 0xa0
000000d5: SHL
000000d6: SUB
000000d7: DUP4
000000d8: AND
000000d9: SWAP1
000000da: PUSH32 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b
000000fb: SWAP1
000000fc: PUSH1 0x00
000000fe: SWAP1
000000ff: LOG2
00000100: DUP1
00000101: MLOAD
00000102: ISZERO
00000103: PUSH2 0x0115
00000106: JUMPI
00000107: PUSH2 0x0110
0000010a: DUP3
0000010b: DUP3
0000010c: PUSH2 0x01cc
0000010f: JUMP
00000110: JUMPDEST
00000111: POP
00000112: POP
00000113: POP
00000114: JUMP
00000115: JUMPDEST
00000116: PUSH2 0x00ae
00000119: PUSH2 0x0242
0000011c: JUMP
0000011d: JUMPDEST
0000011e: PUSH1 0x00
00000120: PUSH2 0x013e
00000123: PUSH1 0x00
00000125: DUP1
00000126: MLOAD
00000127: PUSH1 0x20
00000129: PUSH2 0x0433
0000012c: DUP4
0000012d: CODECOPY
0000012e: DUP2
0000012f: MLOAD
00000130: SWAP2
00000131: MSTORE
00000132: SLOAD
00000133: PUSH1 0x01
00000135: PUSH1 0x01
00000137: PUSH1 0xa0
00000139: SHL
0000013a: SUB
0000013b: AND
0000013c: SWAP1
0000013d: JUMP
0000013e: JUMPDEST
0000013f: SWAP1
00000140: POP
00000141: SWAP1
00000142: JUMP
00000143: JUMPDEST
00000144: CALLDATASIZE
00000145: PUSH1 0x00
00000147: DUP1
00000148: CALLDATACOPY
00000149: PUSH1 0x00
0000014b: DUP1
0000014c: CALLDATASIZE
0000014d: PUSH1 0x00
0000014f: DUP5
00000150: GAS
00000151: DELEGATECALL
00000152: RETURNDATASIZE
00000153: PUSH1 0x00
00000155: DUP1
00000156: RETURNDATACOPY
00000157: DUP1
00000158: DUP1
00000159: ISZERO
0000015a: PUSH2 0x0162
0000015d: JUMPI
0000015e: RETURNDATASIZE
0000015f: PUSH1 0x00
00000161: RETURN
00000162: JUMPDEST
00000163: RETURNDATASIZE
00000164: PUSH1 0x00
00000166: REVERT
00000167: JUMPDEST
00000168: DUP1
00000169: PUSH1 0x01
0000016b: PUSH1 0x01
0000016d: PUSH1 0xa0
0000016f: SHL
00000170: SUB
00000171: AND
00000172: EXTCODESIZE
00000173: PUSH1 0x00
00000175: SUB
00000176: PUSH2 0x019d
00000179: JUMPI
0000017a: DUP1
0000017b: PUSH1 0x40
0000017d: MLOAD
0000017e: PUSH4 0x4c9c8ce3
00000183: PUSH1 0xe0
00000185: SHL
00000186: DUP2
00000187: MSTORE
00000188: PUSH1 0x04
0000018a: ADD
0000018b: PUSH2 0x0194
0000018e: SWAP2
0000018f: SWAP1
00000190: PUSH2 0x03ef
00000193: JUMP
00000194: JUMPDEST
00000195: PUSH1 0x40
00000197: MLOAD
00000198: DUP1
00000199: SWAP2
0000019a: SUB
0000019b: SWAP1
0000019c: REVERT
0000019d: JUMPDEST
0000019e: PUSH1 0x00
000001a0: DUP1
000001a1: MLOAD
000001a2: PUSH1 0x20
000001a4: PUSH2 0x0433
000001a7: DUP4
000001a8: CODECOPY
000001a9: DUP2
000001aa: MLOAD
000001ab: SWAP2
000001ac: MSTORE
000001ad: DUP1
000001ae: SLOAD
000001af: PUSH1 0x01
000001b1: PUSH1 0x01
000001b3: PUSH1 0xa0
000001b5: SHL
000001b6: SUB
000001b7: NOT
000001b8: AND
000001b9: PUSH1 0x01
000001bb: PUSH1 0x01
000001bd: PUSH1 0xa0
000001bf: SHL
000001c0: SUB
000001c1: SWAP3
000001c2: SWAP1
000001c3: SWAP3
000001c4: AND
000001c5: SWAP2
000001c6: SWAP1
000001c7: SWAP2
000001c8: OR
000001c9: SWAP1
000001ca: SSTORE
000001cb: JUMP
000001cc: JUMPDEST
000001cd: PUSH1 0x60
000001cf: PUSH1 0x00
000001d1: DUP1
000001d2: DUP5
000001d3: PUSH1 0x01
000001d5: PUSH1 0x01
000001d7: PUSH1 0xa0
000001d9: SHL
000001da: SUB
000001db: AND
000001dc: DUP5
000001dd: PUSH1 0x40
000001df: MLOAD
000001e0: PUSH2 0x01e9
000001e3: SWAP2
000001e4: SWAP1
000001e5: PUSH2 0x0403
000001e8: JUMP
000001e9: JUMPDEST
000001ea: PUSH1 0x00
000001ec: PUSH1 0x40
000001ee: MLOAD
000001ef: DUP1
000001f0: DUP4
000001f1: SUB
000001f2: DUP2
000001f3: DUP6
000001f4: GAS
000001f5: DELEGATECALL
000001f6: SWAP2
000001f7: POP
000001f8: POP
000001f9: RETURNDATASIZE
000001fa: DUP1
000001fb: PUSH1 0x00
000001fd: DUP2
000001fe: EQ
000001ff: PUSH2 0x0224
00000202: JUMPI
00000203: PUSH1 0x40
00000205: MLOAD
00000206: SWAP2
00000207: POP
00000208: PUSH1 0x1f
0000020a: NOT
0000020b: PUSH1 0x3f
0000020d: RETURNDATASIZE
0000020e: ADD
0000020f: AND
00000210: DUP3
00000211: ADD
00000212: PUSH1 0x40
00000214: MSTORE
00000215: RETURNDATASIZE
00000216: DUP3
00000217: MSTORE
00000218: RETURNDATASIZE
00000219: PUSH1 0x00
0000021b: PUSH1 0x20
0000021d: DUP5
0000021e: ADD
0000021f: RETURNDATACOPY
00000220: PUSH2 0x0229
00000223: JUMP
00000224: JUMPDEST
00000225: PUSH1 0x60
00000227: SWAP2
00000228: POP
00000229: JUMPDEST
0000022a: POP
0000022b: SWAP2
0000022c: POP
0000022d: SWAP2
0000022e: POP
0000022f: PUSH2 0x0239
00000232: DUP6
00000233: DUP4
00000234: DUP4
00000235: PUSH2 0x0261
00000238: JUMP
00000239: JUMPDEST
0000023a: SWAP6
0000023b: SWAP5
0000023c: POP
0000023d: POP
0000023e: POP
0000023f: POP
00000240: POP
00000241: JUMP
00000242: JUMPDEST
00000243: CALLVALUE
00000244: ISZERO
00000245: PUSH2 0x0079
00000248: JUMPI
00000249: PUSH1 0x40
0000024b: MLOAD
0000024c: PUSH4 0xb398979f
00000251: PUSH1 0xe0
00000253: SHL
00000254: DUP2
00000255: MSTORE
00000256: PUSH1 0x04
00000258: ADD
00000259: PUSH1 0x40
0000025b: MLOAD
0000025c: DUP1
0000025d: SWAP2
0000025e: SUB
0000025f: SWAP1
00000260: REVERT
00000261: JUMPDEST
00000262: PUSH1 0x60
00000264: DUP3
00000265: PUSH2 0x0276
00000268: JUMPI
00000269: PUSH2 0x0271
0000026c: DUP3
0000026d: PUSH2 0x02b7
00000270: JUMP
00000271: JUMPDEST
00000272: PUSH2 0x02b0
00000275: JUMP
00000276: JUMPDEST
00000277: DUP2
00000278: MLOAD
00000279: ISZERO
0000027a: DUP1
0000027b: ISZERO
0000027c: PUSH2 0x028d
0000027f: JUMPI
00000280: POP
00000281: PUSH1 0x01
00000283: PUSH1 0x01
00000285: PUSH1 0xa0
00000287: SHL
00000288: SUB
00000289: DUP5
0000028a: AND
0000028b: EXTCODESIZE
0000028c: ISZERO
0000028d: JUMPDEST
0000028e: ISZERO
0000028f: PUSH2 0x02ad
00000292: JUMPI
00000293: DUP4
00000294: PUSH1 0x40
00000296: MLOAD
00000297: PUSH4 0x9996b315
0000029c: PUSH1 0xe0
0000029e: SHL
0000029f: DUP2
000002a0: MSTORE
000002a1: PUSH1 0x04
000002a3: ADD
000002a4: PUSH2 0x0194
000002a7: SWAP2
000002a8: SWAP1
000002a9: PUSH2 0x03ef
000002ac: JUMP
000002ad: JUMPDEST
000002ae: POP
000002af: DUP1
000002b0: JUMPDEST
000002b1: SWAP4
000002b2: SWAP3
000002b3: POP
000002b4: POP
000002b5: POP
000002b6: JUMP
000002b7: JUMPDEST
000002b8: DUP1
000002b9: MLOAD
000002ba: ISZERO
000002bb: PUSH2 0x02c7
000002be: JUMPI
000002bf: DUP1
000002c0: MLOAD
000002c1: DUP1
000002c2: DUP3
000002c3: PUSH1 0x20
000002c5: ADD
000002c6: REVERT
000002c7: JUMPDEST
000002c8: PUSH1 0x40
000002ca: MLOAD
000002cb: PUSH4 0x0a12f521
000002d0: PUSH1 0xe1
000002d2: SHL
000002d3: DUP2
000002d4: MSTORE
000002d5: PUSH1 0x04
000002d7: ADD
000002d8: PUSH1 0x40
000002da: MLOAD
000002db: DUP1
000002dc: SWAP2
000002dd: SUB
000002de: SWAP1
000002df: REVERT
000002e0: JUMPDEST
000002e1: PUSH1 0x00
000002e3: DUP1
000002e4: DUP6
000002e5: DUP6
000002e6: GT
000002e7: ISZERO
000002e8: PUSH2 0x02f0
000002eb: JUMPI
000002ec: PUSH1 0x00
000002ee: DUP1
000002ef: REVERT
000002f0: JUMPDEST
000002f1: DUP4
000002f2: DUP7
000002f3: GT
000002f4: ISZERO
000002f5: PUSH2 0x02fd
000002f8: JUMPI
000002f9: PUSH1 0x00
000002fb: DUP1
000002fc: REVERT
000002fd: JUMPDEST
000002fe: POP
000002ff: POP
00000300: DUP3
00000301: ADD
00000302: SWAP4
00000303: SWAP2
00000304: SWAP1
00000305: SWAP3
00000306: SUB
00000307: SWAP2
00000308: POP
00000309: JUMP
0000030a: JUMPDEST
0000030b: PUSH4 0x4e487b71
00000310: PUSH1 0xe0
00000312: SHL
00000313: PUSH1 0x00
00000315: MSTORE
00000316: PUSH1 0x41
00000318: PUSH1 0x04
0000031a: MSTORE
0000031b: PUSH1 0x24
0000031d: PUSH1 0x00
0000031f: REVERT
00000320: JUMPDEST
00000321: PUSH1 0x00
00000323: DUP1
00000324: PUSH1 0x40
00000326: DUP4
00000327: DUP6
00000328: SUB
00000329: SLT
0000032a: ISZERO
0000032b: PUSH2 0x0333
0000032e: JUMPI
0000032f: PUSH1 0x00
00000331: DUP1
00000332: REVERT
00000333: JUMPDEST
00000334: DUP3
00000335: CALLDATALOAD
00000336: PUSH1 0x01
00000338: PUSH1 0x01
0000033a: PUSH1 0xa0
0000033c: SHL
0000033d: SUB
0000033e: DUP2
0000033f: AND
00000340: DUP2
00000341: EQ
00000342: PUSH2 0x034a
00000345: JUMPI
00000346: PUSH1 0x00
00000348: DUP1
00000349: REVERT
0000034a: JUMPDEST
0000034b: SWAP2
0000034c: POP
0000034d: PUSH1 0x20
0000034f: DUP4
00000350: ADD
00000351: CALLDATALOAD
00000352: PUSH1 0x01
00000354: PUSH1 0x01
00000356: PUSH1 0x40
00000358: SHL
00000359: SUB
0000035a: DUP1
0000035b: DUP3
0000035c: GT
0000035d: ISZERO
0000035e: PUSH2 0x0366
00000361: JUMPI
00000362: PUSH1 0x00
00000364: DUP1
00000365: REVERT
00000366: JUMPDEST
00000367: DUP2
00000368: DUP6
00000369: ADD
0000036a: SWAP2
0000036b: POP
0000036c: DUP6
0000036d: PUSH1 0x1f
0000036f: DUP4
00000370: ADD
00000371: SLT
00000372: PUSH2 0x037a
00000375: JUMPI
00000376: PUSH1 0x00
00000378: DUP1
00000379: REVERT
0000037a: JUMPDEST
0000037b: DUP2
0000037c: CALLDATALOAD
0000037d: DUP2
0000037e: DUP2
0000037f: GT
00000380: ISZERO
00000381: PUSH2 0x038c
00000384: JUMPI
00000385: PUSH2 0x038c
00000388: PUSH2 0x030a
0000038b: JUMP
0000038c: JUMPDEST
0000038d: PUSH1 0x40
0000038f: MLOAD
00000390: PUSH1 0x1f
00000392: DUP3
00000393: ADD
00000394: PUSH1 0x1f
00000396: NOT
00000397: SWAP1
00000398: DUP2
00000399: AND
0000039a: PUSH1 0x3f
0000039c: ADD
0000039d: AND
0000039e: DUP2
0000039f: ADD
000003a0: SWAP1
000003a1: DUP4
000003a2: DUP3
000003a3: GT
000003a4: DUP2
000003a5: DUP4
000003a6: LT
000003a7: OR
000003a8: ISZERO
000003a9: PUSH2 0x03b4
000003ac: JUMPI
000003ad: PUSH2 0x03b4
000003b0: PUSH2 0x030a
000003b3: JUMP
000003b4: JUMPDEST
000003b5: DUP2
000003b6: PUSH1 0x40
000003b8: MSTORE
000003b9: DUP3
000003ba: DUP2
000003bb: MSTORE
000003bc: DUP9
000003bd: PUSH1 0x20
000003bf: DUP5
000003c0: DUP8
000003c1: ADD
000003c2: ADD
000003c3: GT
000003c4: ISZERO
000003c5: PUSH2 0x03cd
000003c8: JUMPI
000003c9: PUSH1 0x00
000003cb: DUP1
000003cc: REVERT
000003cd: JUMPDEST
000003ce: DUP3
000003cf: PUSH1 0x20
000003d1: DUP7
000003d2: ADD
000003d3: PUSH1 0x20
000003d5: DUP4
000003d6: ADD
000003d7: CALLDATACOPY
000003d8: PUSH1 0x00
000003da: PUSH1 0x20
000003dc: DUP5
000003dd: DUP4
000003de: ADD
000003df: ADD
000003e0: MSTORE
000003e1: DUP1
000003e2: SWAP6
000003e3: POP
000003e4: POP
000003e5: POP
000003e6: POP
000003e7: POP
000003e8: POP
000003e9: SWAP3
000003ea: POP
000003eb: SWAP3
000003ec: SWAP1
000003ed: POP
000003ee: JUMP
000003ef: JUMPDEST
000003f0: PUSH1 0x01
000003f2: PUSH1 0x01
000003f4: PUSH1 0xa0
000003f6: SHL
000003f7: SUB
000003f8: SWAP2
000003f9: SWAP1
000003fa: SWAP2
000003fb: AND
000003fc: DUP2
000003fd: MSTORE
000003fe: PUSH1 0x20
00000400: ADD
00000401: SWAP1
00000402: JUMP
00000403: JUMPDEST
00000404: PUSH1 0x00
00000406: DUP3
00000407: MLOAD
00000408: PUSH1 0x00
0000040a: JUMPDEST
0000040b: DUP2
0000040c: DUP2
0000040d: LT
0000040e: ISZERO
0000040f: PUSH2 0x0424
00000412: JUMPI
00000413: PUSH1 0x20
00000415: DUP2
00000416: DUP7
00000417: ADD
00000418: DUP2
00000419: ADD
0000041a: MLOAD
0000041b: DUP6
0000041c: DUP4
0000041d: ADD
0000041e: MSTORE
0000041f: ADD
00000420: PUSH2 0x040a
00000423: JUMP
00000424: JUMPDEST
00000425: POP
00000426: PUSH1 0x00
00000428: SWAP3
00000429: ADD
0000042a: SWAP2
0000042b: DUP3
0000042c: MSTORE
0000042d: POP
0000042e: SWAP2
0000042f: SWAP1
00000430: POP
00000431: JUMP
00000432: INVALID
00000433: CALLDATASIZE
00000434: ADDMOD
00000435: SWAP5
00000436: LOG1
00000437: EXTCODESIZE
00000438: LOG1
00000439: LOG3
0000043a: UNKNOWN(0x21)
0000043b: MOD
0000043c: PUSH8 0xc828492db98dca3e
00000445: KECCAK256
00000446: PUSH23 0xcc3735a920a3ca505d382bbca264697066735822122092
0000045e: UNKNOWN(0x22)
0000045f: EXTCODEHASH
00000460: UNKNOWN(0xE4)
00000461: UNKNOWN(0xE2)
00000462: SWAP5
00000463: UNKNOWN(0xBC)
00000464: UNKNOWN(0xAE)
00000465: CREATE2
00000466: MSIZE
00000467: SAR
00000468: BALANCE
00000469: SWAP15
0000046a: UNKNOWN(0x0D)
0000046b: PUSH9 0x7141ba94e9cc225e29
00000475: UNKNOWN(0x1F)
00000476: PUSH25

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: 0x826da0a762f5fb2615cfa70400928ea66b4a1beab129e3ee3fae5561b8216a8d
call_targets: 0x6ac64c4760e0590f88233b2046810e87e0354324
target_varies: no
classification: constant_target
validation_json
{
    "sink": "DELEGATECALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x826da0a762f5fb2615cfa70400928ea66b4a1beab129e3ee3fae5561b8216a8d",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0x6ac64c4760e0590f88233b2046810e87e0354324"
    ],
    "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": 337,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        },
        {
            "pc": 501,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        }
    ],
    "guards_detected": [],
    "delegatecall_count": 2,
    "untrusted_delegatecall_count": 2
}
high reentrancy codex `fillOffer` can be reentered before `filledAmount` is updated, allowing overfilled orders 85 no
view
evidence_json
{
    "evidence": "`fillOffer` performs `iexToken.safeTransferFrom(...)` before calling `_fillOffer(...)`. `_fillOffer` is the only place that increments `offer.filledAmount` and closes the offer.",
    "locations": [
        "src/FrontrunMarket.sol:381",
        "src/FrontrunMarket.sol:410",
        "src/FrontrunMarket.sol:412",
        "src/FrontrunMarket.sol:763",
        "src/FrontrunMarket.sol:770"
    ],
    "recommendation": "Make `fillOffer` non-reentrant, or expose only a guarded external entrypoint and keep the core logic internal. Update `offer.filledAmount`/status before any external token transfer, or otherwise re-check remaining capacity after the transfer returns.",
    "exploit_scenario": "If a whitelisted collateral token invokes callbacks during `transferFrom` (for example an ERC777-style token or a callback-capable ERC20), an attacker can fill the same offer reentrantly from a contract wallet. Each nested call observes the old `offer.filledAmount`, so multiple full-sized orders are created against one offer. The attacker can then self-settle those duplicated orders and extract more `value + collateral` than was originally escrowed, draining pooled liquidity or leaving the market insolvent."
}
high other codex Nominal ERC20 accounting makes fee-on-transfer collateral tokens insolvent 85 no
view
evidence_json
{
    "evidence": "Both `newOffer` and `fillOffer` transfer ERC20s and then keep using caller-supplied `value` / derived `collateral`; no post-transfer balance check exists before later payouts in `forceCancelOrder`, `settleFilled`, and `settleCancelled`.",
    "locations": [
        "src/FrontrunMarket.sol:322",
        "src/FrontrunMarket.sol:343",
        "src/FrontrunMarket.sol:381",
        "src/FrontrunMarket.sol:410",
        "src/FrontrunMarket.sol:526",
        "src/FrontrunMarket.sol:575"
    ],
    "recommendation": "Only allow plain ERC20 collateral tokens, or account using balance deltas before and after each transfer. Reject deposits where the actual received amount is smaller than the expected amount.",
    "exploit_scenario": "If governance ever accepts a deflationary / fee-on-transfer token as collateral, the market records `value` and `collateral` using the requested amounts instead of the actual tokens received. An attacker can self-trade using that token so the contract receives less than it promises to pay back on cancel / settle. The shortfall is then paid from unrelated users' escrow if available, or later exits revert because the vault is underfunded."
}
medium access_control codex Owner can confiscate live escrow by de-whitelisting a token before `withdrawStuckToken` 75 no
view
evidence_json
{
    "evidence": "`setAcceptedTokens` can flip any token's accepted status at any time, and `withdrawStuckToken` transfers the full contract balance whenever `acceptedTokens[_token]` is false.",
    "locations": [
        "src/FrontrunMarket.sol:656",
        "src/FrontrunMarket.sol:660",
        "src/FrontrunMarket.sol:780",
        "src/FrontrunMarket.sol:782",
        "src/FrontrunMarket.sol:784"
    ],
    "recommendation": "Track escrowed balances per collateral token and forbid withdrawing any amount backing live offers/orders. `withdrawStuckToken` should not rely only on the current accepted-token flag.",
    "exploit_scenario": "While users still have open offers or orders denominated in token `X`, the owner can call `setAcceptedTokens([X], false)` and immediately follow with `withdrawStuckToken(X, owner)`. Because the withdrawal gate only checks the current whitelist bit, the function transfers the entire balance of `X`, including funds backing active positions. Those positions then remain recorded on-chain but cannot be settled or cancelled successfully."
}
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 other detector CREATE/CREATE2 reachable 45 no no
view
trace_id: 0xa669044104f7a80bf9c8801bf53b24e8789551c80260853b11d82347b90cfc45
validation_json
{
    "sink": "CREATE",
    "errors": 0,
    "status": "unknown",
    "attempts": 2,
    "trace_id": "0xa669044104f7a80bf9c8801bf53b24e8789551c80260853b11d82347b90cfc45",
    "confirmed": false,
    "trace_mode": "structLogs",
    "call_targets": null,
    "matched_probe": null,
    "target_varies": null,
    "classification": "no_sink"
}
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
{
    "create_count": 0,
    "create2_count": 1
}
low other cast Contract creation opcode present 60 no
view
evidence_json
{
    "notes": "runtime can deploy contracts dynamically via CREATE/CREATE2",
    "create_count": 0,
    "create2_count": 1
}
info other cast Heavy EXTCODE*/BALANCE usage 55 no
view
evidence_json
{
    "notes": "frequent external code or balance checks; may indicate introspection or validation logic",
    "ext_balance_count": 5
}