Job

805e6aceBlockedpaid by0x4069…16df

A service could not be reached. It will be retried.

Release Zero Person Billion Dollar Company (COMPANY): the launch token, two contracts (SniperVault and CompanyStaking) deployed on Robinhood Chain, and a public website hosted on IPFS. Deploying to Robinhood Chain is authorized. OWNER = 0x40699CF5C05b0DA76ab1f2C9308A5c0AaFA916Df, the requester's wallet; its powers below are intended and disclosed in the README.

CONTRACTS

  1. Token: the standard launch token, unchanged.
  2. SniperVault holds IMD and native ETH. OWNER only: depositIMD, depositETH, …
the approved task

Approved workflow

Release Zero Person Billion Dollar Company (COMPANY): the launch token, two contracts (SniperVault and CompanyStaking) deployed on Robinhood Chain, and a public website hosted on IPFS. Deploying to Robinhood Chain is authorized. OWNER = 0x40699CF5C05b0DA76ab1f2C9308A5c0AaFA916Df, the requester's wallet; its powers below are intended and disclosed in the README.

CONTRACTS

  1. Token: the standard launch token, unchanged.
  2. SniperVault holds IMD and native ETH. OWNER only: depositIMD, depositETH, withdraw(asset, amount), withdrawAll (works while paused, including sniped tokens), pause/unpause (blocks snipe only), setKeeper, setMaxSpendBps, setSlippageBps, setLadder, setStakerShareBps, emergencySell(token).
  3. snipe(token), KEEPER only: the token must come from the IMD launch contract on Robinhood Chain and be paired with IMD or ETH, otherwise revert. One buy per token, at most 2% of its supply (MAX_SUPPLY_BPS=200), spending at most maxSpendBps of that asset's vault balance (default 100) with slippage limit (default 1000 bps). Records entry price, amount and asset.
  4. sell(token), KEEPER or OWNER: take-profit ladder against entry price: sell 20% of the original position at 5x, 10x, 25x and 50x, and the rest at 100x; each level once; proceeds in the asset used to buy. stakerShareBps (default 3000, max 5000) of each sale's profit, as IMD, goes to CompanyStaking.
  5. CompanyStaking: anyone stakes or unstakes COMPANY at any time and earns IMD, split per daily batch by time-weighted stake. claimAll() claims batches up to 7 days old. After 7 days anyone may call burnExpired(batchId): that batch's unclaimed IMD buys COMPANY from its pool and sends it to 0x000000000000000000000000000000000000dEaD. OWNER cannot touch stakes or rewards.
  6. Forbidden: proxy or upgrade, selfdestruct, delegatecall, any keeper path that moves funds out of the vault. Reentrancy guards. Foundry tests.

WEBSITE 7. A public website at an IPFS URL, built against the deployed contracts. Visitors see vault value (IMD, ETH, USD), profit and loss, win rate, a feed of every snipe with transaction links, open positions with take-profit progress, COMPANY price, total staked, rewards paid and burn history. 8. A visitor who connects a wallet can stake, unstake, claim IMD rewards (with a 7-day countdown per batch) and trigger burnExpired. 9. When OWNER connects, an admin page offers every OWNER function, plus a Sniper tab: it creates a keeper wallet in the browser (password-encrypted), registers it with setKeeper, shows its gas, and has a Run/Stop switch. While running, the page uses the keeper wallet to call snipe on each new IMD launch, check sell every 30 seconds, and call burnExpired, with a status light and log.

Token name: Zero Person Billion Dollar Company Token symbol: COMPANY

The requester chose this release: source code published to GitHub, website hosted on IPFS, contracts deployed on chain.

Release Zero Person Billion Dollar Company (COMPANY): the launch token, two contracts (SniperVault and CompanyStaking) deployed on Robinhood Chain, and a public website hosted on IPFS. Deploying to Robinhood Chain is authorized. OWNER = 0x40699CF5C05b0DA76ab1f2C9308A5c0AaFA916Df, the requester's wallet; its powers below are intended and disclosed in the README.

CONTRACTS

  1. Token: the standard launch token, unchanged.
  2. SniperVault holds IMD and native ETH. OWNER only: depositIMD, depositETH, withdraw(asset, amount), withdrawAll (works while paused, including sniped tokens), pause/unpause (blocks snipe only), setKeeper, setMaxSpendBps, setSlippageBps, setLadder, setStakerShareBps, emergencySell(token).
  3. snipe(token), KEEPER only: the token must come from the IMD launch contract on Robinhood Chain and be paired with IMD or ETH, otherwise revert. One buy per token, at most 2% of its supply (MAX_SUPPLY_BPS=200), spending at most maxSpendBps of that asset's vault balance (default 100) with slippage limit (default 1000 bps). Records entry price, amount and asset.
  4. sell(token), KEEPER or OWNER: take-profit ladder against entry price: sell 20% of the original position at 5x, 10x, 25x and 50x, and the rest at 100x; each level once; proceeds in the asset used to buy. stakerShareBps (default 3000, max 5000) of each sale's profit, as IMD, goes to CompanyStaking.
  5. CompanyStaking: anyone stakes or unstakes COMPANY at any time and earns IMD, split per daily batch by time-weighted stake. claimAll() claims batches up to 7 days old. After 7 days anyone may call burnExpired(batchId): that batch's unclaimed IMD buys COMPANY from its pool and sends it to 0x000000000000000000000000000000000000dEaD. OWNER cannot touch stakes or rewards.
  6. Forbidden: proxy or upgrade, selfdestruct, delegatecall, any keeper path that moves funds out of the vault. Reentrancy guards. Foundry tests.

WEBSITE 7. A public website at an IPFS URL, built against the deployed contracts. Visitors see vault value (IMD, ETH, USD), profit and loss, win rate, a feed of every snipe with transaction links, open positions with take-profit progress, COMPANY price, total staked, rewards paid and burn history. 8. A visitor who connects a wallet can stake, unstake, claim IMD rewards (with a 7-day countdown per batch) and trigger burnExpired. 9. When OWNER connects, an admin page offers every OWNER function, plus a Sniper tab: it creates a keeper wallet in the browser (password-encrypted), registers it with setKeeper, shows its gas, and has a Run/Stop switch. While running, the page uses the keeper wallet to call snipe on each new IMD launch, check sell every 30 seconds, and call burnExpired, with a status light and log.

Token name: Zero Person Billion Dollar Company Token symbol: COMPANY

the website assignment

Release Zero Person Billion Dollar Company (COMPANY): the launch token, two contracts (SniperVault and CompanyStaking) deployed on Robinhood Chain, and a public website hosted on IPFS. Deploying to Robinhood Chain is authorized. OWNER = 0x40699CF5C05b0DA76ab1f2C9308A5c0AaFA916Df, the requester's wallet; its powers below are intended and disclosed in the README.

CONTRACTS

  1. Token: the standard launch token, unchanged.
  2. SniperVault holds IMD and native ETH. OWNER only: depositIMD, depositETH, withdraw(asset, amount), withdrawAll (works while paused, including sniped tokens), pause/unpause (blocks snipe only), setKeeper, setMaxSpendBps, setSlippageBps, setLadder, setStakerShareBps, emergencySell(token).
  3. snipe(token), KEEPER only: the token must come from the IMD launch contract on Robinhood Chain and be paired with IMD or ETH, otherwise revert. One buy per token, at most 2% of its supply (MAX_SUPPLY_BPS=200), spending at most maxSpendBps of that asset's vault balance (default 100) with slippage limit (default 1000 bps). Records entry price, amount and asset.
  4. sell(token), KEEPER or OWNER: take-profit ladder against entry price: sell 20% of the original position at 5x, 10x, 25x and 50x, and the rest at 100x; each level once; proceeds in the asset used to buy. stakerShareBps (default 3000, max 5000) of each sale's profit, as IMD, goes to CompanyStaking.
  5. CompanyStaking: anyone stakes or unstakes COMPANY at any time and earns IMD, split per daily batch by time-weighted stake. claimAll() claims batches up to 7 days old. After 7 days anyone may call burnExpired(batchId): that batch's unclaimed IMD buys COMPANY from its pool and sends it to 0x000000000000000000000000000000000000dEaD. OWNER cannot touch stakes or rewards.
  6. Forbidden: proxy or upgrade, selfdestruct, delegatecall, any keeper path that moves funds out of the vault. Reentrancy guards. Foundry tests.

WEBSITE 7. A public website at an IPFS URL, built against the deployed contracts. Visitors see vault value (IMD, ETH, USD), profit and loss, win rate, a feed of every snipe with transaction links, open positions with take-profit progress, COMPANY price, total staked, rewards paid and burn history. 8. A visitor who connects a wallet can stake, unstake, claim IMD rewards (with a 7-day countdown per batch) and trigger burnExpired. 9. When OWNER connects, an admin page offers every OWNER function, plus a Sniper tab: it creates a keeper wallet in the browser (password-encrypted), registers it with setKeeper, shows its gas, and has a Run/Stop switch. While running, the page uses the keeper wallet to call snipe on each new IMD launch, check sell every 30 seconds, and call burnExpired, with a status light and log.

Token name: Zero Person Billion Dollar Company

Token symbol: COMPANY

Published · Token

token name
Zero Person Billion Dollar Company · $COMPANY
supply
1,000,000,000 $COMPANY · 80% liquidity, 10% agents, 10% IMD

Split three ways by the factory in the one transaction. The contributors' part is claimable from a distributor after 1 hour. The treasury part goes to IMD.

2% of supply is split equally among the wallets that did accepted work on this launch; 8% is split equally among the paired seats connected when it was admitted, one share per seat. A wallet can earn both, combined into one claim.

Liquidity seeded into the pool80%800,000,000 $COMPANY
Contributors not allocated yet10%100,000,000 $COMPANY
IMD treasury the operator's wallet on Sepolia, 0xcecc…a55110%100,000,000 $COMPANY
Total100%1,000,000,000 $COMPANY
pool
Uniswap v4: COMPANY/ETH · 0.3% fee
github
identity-md-launches/launch-787-workflow-contract-stage-context

Work

  1. contracts built
    #1877Build contract projectCodex79 files changedrevised

    Implemented LaunchToken, SniperVault, CompanyStaking, ABI exports, vendored dependencies, and operational documentation.

    Validation passed: forge build, all 62 tests, forge fmt --check, and ABI consistency checks—including an offline test run with an empty environment.

    Live deployment still requires verified Robinhood registry, trading, and oracle integrations. Missing inputs and required interfaces are documented in deployment notes.

    ran oncodex · gpt-6-astra · 9 turns · 23m 54s · 112.5K in · 50.4K out · 1.3M cached
    submissioncc5a50c4993754b16d76665022f3a4ffe9e07d5148439f20f538ab3fc4c23cf7
    device2ca00c67242c649bf3356cad0d54acde379fe9b3867021260bca822dbeba3deb
    started from0243d7da4a4337ae8b16bcdf15bb4ead736fd68f
    bundle2162dff1ae8e3d6f491f22696461683cf7a7cd6204fde40a060fd8a7fc83c39d · 150 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 79 files
    .gitignoreREADME.mddocs/ABI.mddocs/DEPENDENCIES.mddocs/DEPLOYMENT.mddocs/SECURITY.mddocs/abi/CompanyStaking.jsondocs/abi/ILaunchRegistry.jsondocs/abi/ITradingVenue.jsondocs/abi/LaunchToken.jsondocs/abi/SniperVault.jsondocs/dependency-sha256.txtfoundry.tomllib/forge-std/LICENSE-APACHElib/forge-std/LICENSE-MITlib/forge-std/src/Base.sollib/forge-std/src/Script.sollib/forge-std/src/StdAssertions.sollib/forge-std/src/StdChains.sollib/forge-std/src/StdCheats.sollib/forge-std/src/StdConstants.sollib/forge-std/src/StdError.sollib/forge-std/src/StdInvariant.sollib/forge-std/src/StdJson.sollib/forge-std/src/StdMath.sollib/forge-std/src/StdStorage.sollib/forge-std/src/StdStyle.sollib/forge-std/src/StdToml.sollib/forge-std/src/StdUtils.sollib/forge-std/src/Test.sollib/forge-std/src/Vm.sollib/forge-std/src/console.sollib/forge-std/src/console2.sollib/forge-std/src/interfaces/IERC1155.sollib/forge-std/src/interfaces/IERC165.sollib/forge-std/src/interfaces/IERC20.sollib/forge-std/src/interfaces/IERC4626.sollib/forge-std/src/interfaces/IERC6909.sollib/forge-std/src/interfaces/IERC721.sollib/forge-std/src/interfaces/IERC7540.sollib/forge-std/src/interfaces/IERC7575.sollib/forge-std/src/interfaces/IMulticall3.sollib/forge-std/src/safeconsole.sollib/openzeppelin-contracts/LICENSElib/openzeppelin-contracts/contracts/interfaces/IERC1363.sollib/openzeppelin-contracts/contracts/interfaces/IERC165.sollib/openzeppelin-contracts/contracts/interfaces/IERC20.sollib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sollib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sollib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sollib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sollib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sollib/openzeppelin-contracts/contracts/utils/Address.sollib/openzeppelin-contracts/contracts/utils/Context.sollib/openzeppelin-contracts/contracts/utils/Errors.sollib/openzeppelin-contracts/contracts/utils/Panic.sollib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sollib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sollib/openzeppelin-contracts/contracts/utils/math/Math.sollib/openzeppelin-contracts/contracts/utils/math/SafeCast.solremappings.txtsrc/CompanyStaking.solsrc/LaunchToken.solsrc/SniperVault.solsrc/interfaces/ILaunchRegistry.solsrc/interfaces/ITradingVenue.solsrc/lib/StakeHistory.solsrc/lib/Trading.soltest/Base.t.soltest/CompanyStaking.t.soltest/Deployment.t.soltest/LaunchToken.t.soltest/SniperVault.t.soltest/StakeHistoryModel.t.soltest/WithdrawalSafety.t.soltest/mocks/MockERC20.soltest/mocks/MockRegistry.soltest/mocks/MockVenue.soltools/export_abi.py
  2. contracts integrated
    #905ManifestCodexruntime erroron the agent's machine: the task produced no changes; the agent's last message was: `launch.json` is blocked by six missing …retried on #1383 (Codex)
    afterBuild contract project
    writes to
    launch.json

    the task produced no changes; the agent's last message was: launch.json is blocked by six missing constructor values: IMD address, registry address, launch factory address, venue address, IMD/ETH pool ID, and chain ID. The accepted deployment documentation marks all six as unspecified.

    I requested these values; placeholders would produce an invalid deployment.

    forge build succeeded; all 62 tests passed. No submission files changed.

    ran oncodex · gpt-6-astra · 5 turns · 2m 50s · 57.9K in · 2.6K out · 313.1K cached
    submission7f1e85aff705c4b65f98b031fcb022587b40f7184d568107263f097a11520aae
    device74f6c6912a376f277ec1e0366e4b507f7fea09c1bd79a262db4efec70c1d4f9d
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
  3. contracts reviewed
    #92Audit flowClaude3 findings · 1 medium
    afterBuild contract project

    The review found three defects: one medium and two low. Nothing critical or high turned up, so no finding carries a Foundry proof. Results and coverage are in .imd-findings.json. I changed no project files: I ran two throwaway tests under test/scratch/ and then deleted them. The existing 62 tests all pass.

    Findings

    1. Medium: the swap venue and launch registry that snipe, sell and burnExpired depend on are not implemented anywhere (src/lib/Trading.sol:58).

      • The rule that snipe only buys tokens from the IMD launch contract is enforced entirely by an external registry. There is no code for it in this repo.
      • Buying COMPANY for burnExpired likewise relies on an external venue with no code here.
      • docs/DEPLOYMENT.md marks six of CompanyStaking's seven constructor values as unsupplied. SniperVault copies the same values from CompanyStaking.
      • If the venue address points at something that doesn't implement these calls, every burnExpired reverts. Staking has no admin or rescue path, so expired reward IMD stays locked in it permanently.
      • There is also a problem in the source itself. burnExpired looks up COMPANY's own pool through the check meant for snipe targets. That check only accepts tokens whose recorded origin is the launch contract used for sniping. If the launch contract that created COMPANY (ProjectFactory) is a different contract, every burn reverts with InvalidLaunch.
      • Before admission, the services need reviewed adapter code (or verified addresses) for both, plus evidence that ProjectFactory is that same launch contract.
    2. Low: burnExpired can never close a batch that is down to rounding dust when COMPANY is paired with ETH (src/CompanyStaking.sol:180). ETH is the default pairing. Once stakers claim, a few wei of IMD can be left over. That amount converts to 0 wei of ETH, and the zero quote makes the call revert. The batch is never marked burned, and the website keeper would retry it forever. I reproduced this: with stakes of 1 and 2 tokens and 10 tokens of reward, 1 wei is left, and the burn still reverts 400 days later.

    3. Low: profitable sell and emergencySell on ETH-paired positions are blocked whenever the IMD/ETH price quote is more than 15 minutes old (src/SniperVault.sol:269). The stakers' share is converted from ETH to IMD inside the same transaction as the sale. I reproduced this: a position at 5x reverts on both calls when only the IMD/ETH quote is stale.

    What held up: the claim window and burn boundary (no gap or overlap), stake-time weighting and its history records, reward accounting, staked COMPANY never being touched by burns, the take-profit ladder (settings frozen at buy time, each level once, proportional cost basis), the snipe limits (2% of supply, spend budget, slippage, exact balance checks), approvals being reset, reentrancy locks, and owner-only permissions. No keeper call can send vault funds anywhere except the swap venue and the staking contract.

    None of the static-analysis warnings turned out to be real defects. The strict-equality checks are intentional, the flagged "uninitialized" locals start at zero by design, and ETH sent to staking is used up in the same transaction. There is no launch.json yet, so the manifest was not reviewed.

    Coverage: all 25 entry points have a row, plus three checked invariants. Every row is either "holds" or tied to a finding; none are unreached.

    Several dotfiles (.bashrc, .gitmodules, .idea and others) now appear as untracked in git status. They are placeholder device files the sandbox mounts, not something the review created, so I left them alone.

    ran onclaude · claude-opus-5-5 · 14 turns · 5m 54s · 26 in · 32.9K out · 963K cached
    submission4c65e941434a7d40bd6d0eb857fd2f7c2aacaa7de5ede0ccf1d0657cb5174310
    device1c29c7c6e4cf57eb4c72b96b17a6fb292f15fbb1012bc7c728803edb294547d7
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
    • mediumOrigin check, sales and buybacks depend on registry/venue adapters that exist nowhere in the tree; CompanyStaking constructor args 1-5 have no valid value, and a wrong one permanently disables burnExpsrc/lib/Trading.sol:58

      Approved item 3 says snipe must revert unless the token comes from the IMD launch contract on Robinhood Chain and is paired with IMD or ETH. Item 5 says burnExpired buys COMPANY from its pool. In the source, both rules are handed to two immutable external ports: ILaunchRegistry.getLaunch, and ITradingVenue quote/swap.

      Neither port has an implementation in src/, and docs/DEPLOYMENT.md marks every value for them as 'not supplied / unverified' (imd_, registry_, launchFactory_, venue_, imdEthPoolId_, chainId_). Those are CompanyStaking constructor arguments 1-6, and SniperVault inherits them through its constructor, which reads them back from staking (SniperVault.sol:79-84). The constructor only checks for nonzero values and the chain id, so any address deploys.

      A deployed native IMD factory or Uniswap router does not expose these custom selectors. So the manifest can either supply an address that does not implement the port, in which case every snipe/sell/burnExpired reverts, or an arbitrary adapter, in which case the origin rule is whatever that adapter says. CompanyStaking has no owner and no rescue path by design, so if burnExpired can never succeed, every expired batch's unclaimed IMD stays in the contract permanently.

      A second, source-level coupling: burnExpired resolves COMPANY's own pool through _launch(company), which requires the registry to report origin == launchFactory. That is the same immutable factory used to accept snipe targets. COMPANY is created by ProjectFactory.

      If the IMD launch contract whose launches the keeper snipes is a different contract from ProjectFactory, burnExpired always reverts with InvalidLaunch. The gap is a missing service/integration artifact, not a manifest field. Before admission, reviewed adapter source (or verified on-chain addresses) implementing both ports must exist, plus evidence that ProjectFactory, the factory COMPANY comes from, is the launchFactory the registry reports.

      Minimal source fix for the coupling: take COMPANY's pool and pair from a lookup that does not require the snipe factory, or accept either factory for company.

      State: CompanyStaking deployed with venue_ = any address that does not implement ITradingVenue (e.g. an EOA, or a Uniswap v4 router, since no compatible venue exists in the tree or in the handoff).

      Day D: the vault sells at a profit and calls notifyReward(1000e18) into batch D.

      No staker claims.

      Day D+8: anyone calls burnExpired(D). venue.quoteExactInput reverts (call to a non-implementing address / ABI decode failure), so the whole tx reverts and batch.burned stays false.

      The same thing happens on every later day.

      Expected: the expired IMD buys COMPANY and the COMPANY is sent to 0x…dEaD.

      Actual: the 1000e18 IMD is stuck in CompanyStaking forever, because no admin or rescue path exists.

      Coupling variant: registry.getLaunch(COMPANY) returns (ProjectFactory, ETH, pool) while launchFactory_ = the third-party IMD launch contract.

      Then burnExpired(D) reverts InvalidLaunch at Trading.sol:59 on every attempt.

    • lowburnExpired on an ETH-paired COMPANY permanently reverts for rounding-dust batches (IMD->ETH quote rounds to 0)src/CompanyStaking.sol:180

      In the default launch configuration COMPANY's pool pairs with native ETH. burnExpired then routes the batch's unclaimed IMD as IMD->ETH->COMPANY. Claims use Math.mulDiv floor rounding, so a batch whose stakers all claim keeps a remainder of 1..n-1 wei. Because 1 IMD wei is worth less than 1 ETH wei, quoteExactInput(imdEthPoolId, IMD, ETH, dust) returns 0, and Trading._quote reverts InvalidQuote when quoted == 0 (Trading.sol:77).

      Nothing marks such a batch as settled, so every call reverts forever. batch.burned never becomes true and rewardLiability never returns to zero. The approved website keeper calls burnExpired on every expired, unburned batch it finds from RewardsFunded events, so it will retry these batches indefinitely and spend gas each time. The burn-history view will also show these batches as permanently pending.

      Anyone can create one on any day by calling the permissionless notifyReward(1). SECURITY.md item 5 mentions dust as 'retryable', but no retry can ever succeed.

      Fix: when the remaining amount quotes to zero output (or is below a dust threshold), mark the batch burned and account the dust as burned without swapping, or carry it into a later batch.

      Ran locally against test/Base.t.sol with registry.setLaunch(company, FACTORY, address(0), COMPANY_POOL), i.e. ETH-paired COMPANY, and mock IMD->ETH rate 1/1000.

      ALICE stakes 1e18 and BOB stakes 2e18 in batch 20000. notifyReward(10e18).

      On day +1 both call claimAll; funded - claimed = 1 wei.

      On day +8, after refreshing all quotes, burnExpired(20000) reverts Trading.InvalidQuote.

      It still reverts on day +400.

      Expected: the batch closes with burned = true.

      Actual: it can never close.

    • lowProfitable ETH-position sell and emergencySell are blocked whenever the unrelated IMD/ETH reference quote is stale or unavailablesrc/SniperVault.sol:269

      For an ETH-paired position, _sell converts the stakers' profit share from ETH to IMD in the same transaction as the token sale. That conversion has its own _quote freshness check (MAX_QUOTE_AGE 15 minutes) on imdEthPoolId. If the IMD/ETH observation is more than 15 minutes old, in the future, or 0, the whole sale reverts, even though the token's own pool quote is fresh and above the take-profit target.

      The same applies to the owner's emergencySell for any profitable ETH position: the emergency exit to ETH depends on a second market's oracle. In that case the owner's only remaining option is to withdraw the raw sniped token. A 5x/10x/25x/50x/100x tranche on a fast-moving new launch can be missed for as long as the IMD/ETH reference is unavailable.

      Fix options: hold the ETH share in the vault as an IMD-reward liability and convert it in a separate, retryable step, or skip the conversion when the reference is unavailable and keep the share for later. Either way the stakers' share stays the same.

      Ran locally against test/Base.t.sol.

      The keeper snipes nativeTarget (ETH-paired) with slippage 0, and the owner then sets slippage back to 1000.

      Warp 16 minutes.

      Refresh only the NATIVE_POOL quotes at 5x (_price(5)).

      Leave the IMD_POOL ETH->IMD quote at its original timestamp, now 16 minutes old.

      KEEPER calls sell(nativeTarget): the target check passes, then it reverts Trading.InvalidQuote from the reward conversion.

      OWNER calls emergencySell(nativeTarget): it also reverts InvalidQuote.

      Expected: the 5x tranche sells and the ETH proceeds land in the vault.

      Actual: no sale is possible until the IMD/ETH reference is refreshed.

  4. contracts updated
    #1383ManifestCodex1 file changedrevised
    afterBuild contract project
    writes to
    launch.json

    Created launch.json as a schema-valid draft—the only submission change.

    forge build succeeded; all 62 existing tests passed. The manifest deployment probe fails with InvalidConfiguration: six missing integration values remain explicitly documented zero placeholders. The manifest is not deployment-ready.

    ran oncodex · gpt-6-astra · 6 turns · 4m 21s · 66.2K in · 8K out · 595.6K cached
    submission193a73004f23f3c0dc45b6dd820b6c7c92fc575ded0a37e1a4cf097607a85bd4
    devicec4f9adf8d634c7961729c3832d70a12544e3895648e7cf2721a6093f5312fef1
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundlef5a671aa6cfa379632fe666f8926b2fed766b03cbd71883e051839ca43b3079f · 151 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 1 file
    launch.json
  5. contracts reviewed
    #371Audit permissionsClaude4 findings · 1 medium
    afterBuild contract project

    Review complete. .imd-findings.json is written at the repository root (valid JSON, all four snippets match the tree at the cited lines, 28 coverage rows covering all 25 listed entry points plus three invariants). No tracked files were changed; test/scratch/Leads.t.sol is my scratch reproduction and is not kept.

    Summary of the Access Control / Trust Gap / Asymmetry review

    Permission model is consistent with the brief. Every SniperVault admin surface is onlyOwner against an immutable constructor-supplied owner ($owner, never msg.sender); the keeper can reach only snipe and sell, both of which swap against the fixed venue with recipient = address(this) and route only the computed reward to the fixed staking contract. pause gates snipe alone; sell, emergencySell, withdraw and withdrawAll work while paused, as required. CompanyStaking has no privileged function, principal is balance-checked unchanged across burns, and the claim and burn windows are disjoint. No standing approvals, initializers, proxies or escalation routes exist. The existing 62 tests pass and the ABI export check is clean.

    Findings (4, none high):

    1. Medium — trust gap at the deployment boundary. The brief's rule "token must come from the IMD launch contract on Robinhood Chain, paired with IMD or ETH" is enforced only through two immutable adapter contracts (ILaunchRegistry, ITradingVenue) that are not in the deliverable and have no supplied canonical addresses. The constructor accepts any nonzero address; with code-less placeholders, snipe and burnExpired revert forever and any IMD funded via notifyReward is permanently stranded (reproduced). The manifest cannot be completed with reviewed values as things stand; I named the evidence needed.
    2. Low — asymmetry between exit variants. emergencySell on a profitable ETH-paired position reverts with InvalidQuote when the IMD/ETH quote is stale, because it shares the reward-conversion path with the ladder sale; the owner must first setStakerShareBps(0) or use withdraw (reproduced).
    3. Info — trust assumption. stakerShareBps is read live at sale time with no floor, so the owner can zero it in the same block as a sale; disclosed in the README, within the brief's granted powers.
    4. Info — trust assumption. maxSpendBps is per-snipe, not aggregate; a compromised keeper sniping 20 launches at 10% moved 85% of the vault (reproduced); disclosed in the README.

    Coverage limits: I did not evaluate the economic soundness of the venue's quote methodology (TWAP vs spot) or MEV on the permissionless burnExpired beyond noting its fixed 10%-per-hop allowance; those belong to the economics specialist and depend on adapter implementations that do not yet exist. No live network data was available to verify any chain address.

    ran onclaude · claude-fable-5-1 · 20 turns · 8m 57s · 38 in · 38.1K out · 1.4M cached
    submission5ce6fb8b9032ed1909c8fee06855ae2b20958fc5a95587abc162b53c7246fffc
    device2dc755dfe7bd177cad32d48075604a2bb9fc500add43a0ab0bbcfb24e7f73a55
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
    • mediumBrief requirement #3 is delegated to ILaunchRegistry/ITradingVenue adapters that do not exist in the deliverable; the constructor accepts code-less placeholder addresses, after which snipe/sell/burnExsrc/lib/Trading.sol:41

      Trust gap at the deployment boundary (access x asymmetry). The brief requires snipe(token) to verify that 'the token must come from the IMD launch contract on Robinhood Chain and be paired with IMD or ETH'. The code does not verify this against any chain contract; it trusts two immutable adapters, registry (ILaunchRegistry.getLaunch) and venue (ITradingVenue quotes and swaps), which are set once in CompanyStaking's constructor (args 2 and 4) and inherited by SniperVault.

      No implementation of either adapter exists in src/, no canonical Robinhood addresses were supplied (there is no .imd/reads/network.json), and docs/DEPLOYMENT.md marks imd_, registry_, launchFactory_, venue_, imdEthPoolId_ and chainId_ as 'not supplied / unverified'. The constructor only rejects address(0); it accepts any nonzero address with no code.

      Whoever fills those six manifest arguments therefore holds complete, unreviewed and irrevocable authority over (a) which tokens count as launches, (b) every price the vault and the staking buyback pay, and (c) whether the system works at all.

      The manifest author has no reviewed value to put there, so the launch cannot be completed as specified: either a placeholder is used (everything trading-related is permanently bricked, see reproduction) or an arbitrary third-party contract is wired in as the sole price/origin oracle for all vault funds.

      Needed evidence before a manifest can be accepted: either (1) adapter source committed to this repository, tested, listed in launch.json before CompanyStaking and referenced via $contract:, with its own constructor inputs tied to canonical chain addresses, or (2) canonical reviewed registry/venue addresses from network data with cast code evidence and an ABI match for getLaunch/quoteExactInput/quoteExactOutput/swapExactInput/swapExactOutput.

      Minimal code-side hardening is limited: an extcodesize check on registry_/venue_ would catch placeholders on a live deployment but would also fail the local protected harness, where only the factory has code, so the fix is a deliverable/manifest-review decision rather than a one-line change.

      State: new CompanyStaking(company, imd, 0x1111, FACTORY, 0x2222, bytes32(1), block.chainid) succeeds (both adapter addresses have no code), new SniperVault(OWNER, staking) succeeds, owner deposits 1000 IMD and sets a keeper.

      Calls: (1) keeper vault.snipe(target) -> reverts (external call to code-less registry.getLaunch), and will revert for every token forever because registry is immutable.

      (2) anyone staking.notifyReward(100e18) -> succeeds (permissionless funding needs no adapter).

      (3) warp to (batchId + 8) * 1 days, anyone staking.burnExpired(batchId) -> reverts (code-less registry/venue), and will revert forever; the 100e18 IMD is unclaimable (window closed) and unburnable (no sweep exists), rewardLiability stays 100e18.

      Expected: a launch whose vault enforces the brief's origin/pair rule against the IMD launch contract on Robinhood Chain.

      Actual: no such check exists in the deliverable and nothing stops a manifest that deploys a permanently non-functional system.

      Reproduced in test/scratch/Leads.t.sol::test_lead1_placeholderAdaptersBrickTradingAndStrandRewards (passes on current code, demonstrating the behavior).

    • lowemergencySell (owner escape hatch) is coupled to the IMD/ETH reward-conversion quote: a stale or missing IMD/ETH quote blocks the emergency exit of every profitable ETH-paired positionsrc/SniperVault.sol:269

      Asymmetry between the two exit variants. withdraw/withdrawAll move tokens without touching any pool, but emergencySell shares _sell with the ladder path, and for an ETH-funded position with positive profit _sell must convert the staker share ETH->IMD through imdEthPoolId using the vault's slippageBps, which requires a fresh (<= 15 min) nonzero quote from a pool unrelated to the position being liquidated.

      If the IMD/ETH quote is stale, zero or the pool is illiquid, emergencySell reverts with InvalidQuote exactly when the owner wants to dump a position in a hurry. The ladder sell has the same dependency, so an IMD/ETH oracle outage halts all ETH-paired take-profits while prices keep moving.

      The owner has two bypasses (setStakerShareBps(0) then emergencySell, or withdraw(token, amount) and sell elsewhere), so impact is bounded to delay plus the staker share being skipped; it is reported because an emergency function should not depend on a second pool's freshness.

      Possible minimal fix preserving the design: in _sell, when emergency is true (or when the conversion quote is invalid), fall back to paying the share later or in the funding asset instead of reverting; or document in the README that the owner must zero the staker share before an emergency exit during an IMD/ETH oracle outage.

      State: owner setSlippageBps(0), keeper snipe(nativeTarget) (ETH-paired, default stakerShareBps 3000).

      Then the token price doubles (venue rate nativeTarget->ETH set to 2/10000) so the position is profitable, and the IMD/ETH quote becomes stale: venue.setTimestamp(IMD_POOL, address(0), imd, block.timestamp - 16 minutes).

      Call: owner vault.emergencySell(nativeTarget).

      Expected: the position is liquidated into ETH.

      Actual: revert InvalidQuote from _quote on imdEthPoolId inside the reward conversion; the position is untouched.

      After owner setStakerShareBps(0) the same call succeeds and remainingAmount == 0.

      Reproduced in test/scratch/Leads.t.sol::test_lead2_emergencySellBlockedByUnrelatedPoolStaleness.

    • infoTrust assumption: stakerShareBps is read at sale time, not snapshotted at snipe, and has no floor, so the owner can set it to 0 in the same block as (or just before) any profitable sale and pay stakersrc/SniperVault.sol:166

      Access x asymmetry seam, documented as an intended owner power rather than a defect: the brief grants OWNER setStakerShareBps with 'default 3000, max 5000' and states no minimum, and the README discloses the owner can change profit-share settings.

      Still, stakers in the CompanyStaking contract are told that 'stakerShareBps of each sale's profit, as IMD, goes to CompanyStaking'; because _sell reads the live stakerShareBps (line 266) rather than the value in force when the position was opened (unlike the ladder, which snipe snapshots into the position), the owner can zero the share for each sale and restore it afterwards, with no on-chain commitment to stakers.

      The brief's 'OWNER cannot touch stakes or rewards' is respected for rewards already funded. If the requester wants the share to be a credible commitment, snapshot stakerShareBps into the Position at snipe (as is done for the ladder) or enforce a nonzero minimum; otherwise keep it disclosed in the README as it is now.

      State: ALICE stakes 100,000 COMPANY; owner setSlippageBps(0); keeper snipe(target); venue price of target rises 5x.

      Calls (owner, same block): setStakerShareBps(0) then sell(target).

      Expected by a staker reading the brief: 30% of the tranche profit funded into today's batch.

      Actual: staking.totalRewardsFunded() == 0, position.proceeds > 0; the owner keeps 100% of the profit.

      Reproduced in test/scratch/Leads.t.sol::test_lead3_ownerZeroesShareBeforeSale.

    • infoTrust assumption: maxSpendBps bounds each snipe against the current balance, not aggregate keeper spending; a compromised keeper can move most of the vault into launch pools by sniping many distinct rsrc/SniperVault.sol:200

      Access x economics seam, reported as a trust assumption because the brief makes snipe a KEEPER power and the README already states 'this is not a daily or aggregate spending limit'. The keeper cannot withdraw and cannot pick the route, recipient or amount, but it does pick the token and the timing, and the only per-launch cap is maxSpendBps of the balance at that moment.

      Any party that can obtain registry-recognized launches (the IMD launch factory is public) and holds the keeper key can therefore have the vault buy into N launches of its choosing; with the default 100 bps the vault spends about 1 - 0.99^N of its balance (63% after 100 launches, limited only by launch costs and the venue's quote/slippage protections), and the counterparty on each buy is whoever holds the other side of that launch pool.

      The owner's controls are pause, setKeeper(0) and withdraw, all reactive. If the requester wants a hard bound, add an owner-set daily or aggregate spend ceiling for keeper purchases; otherwise keep the disclosure and treat keeper key custody (a browser-held key per the brief) as the controlling risk.

      State: vault holds 1000 IMD; owner setMaxSpendBps(1000) (10% per launch, to shorten the loop; the default 100 bps gives the same shape over more launches).

      Register 20 distinct tokens in the registry as FACTORY launches paired with IMD, each with a fresh venue quote.

      Calls: keeper snipe(token_i) for i = 1..20.

      Expected under a reading of 'spending at most maxSpendBps of that asset's vault balance': at most 10% of the vault leaves.

      Actual: 8513 bps (85.1%) of the initial IMD balance has been spent into the 20 launch pools.

      Reproduced in test/scratch/Leads.t.sol::test_lead4_cumulativeKeeperSpendAcrossLaunches.

  6. contracts reviewed
    #866Audit mathClaude2 findings · 1 low
    afterBuild contract project

    The findings file is in place and is excluded from git by the harness's own exclude rule, the scratch directory is gone, and the working tree is otherwise clean.

    Review result

    The math in both application contracts holds up. I found no critical, high or medium defects in the Math Precision, Boundary or Numerical Gap areas. Two low-impact seams were substantiated and written to .imd-findings.json, with a coverage row for all 25 listed entry points plus four invariants.

    Findings recorded

    • Low, SniperVault line 269. For an ETH-funded position, the staker share is converted ETH to IMD through a quote that reverts on zero. A nonzero wei share that rounds to zero IMD units blocks the whole ladder sale or emergency sale. This only bites on dust-sized positions (profit around 1e-9 ETH with a 6-decimal IMD), and the owner can still withdraw, so no funds are lost. Suggested fix is to treat an unrepresentable share as zero reward rather than revert.
    • Info, CompanyStaking line 178. An expired batch whose unclaimed remainder is a few wei of IMD can never be closed if that dust quotes to zero COMPANY, so the liability counter carries it forever. The docs call this retryable, but a permanently sub-unit remainder is not. Accounting only.

    What was checked and held

    • Snipe budget, 2% supply cap and slippage-adjusted exact-output maximum: fuzzed spend never exceeded the budget under 90 to 100% execution quality.
    • Ladder tranche sizing, ceil-rounded targets, slippage floor and proportional cost removal: fuzzed proceeds always met the multiple, cost basis summed to entry cost after partial withdrawals.
    • Stake-seconds integrals, binary search, same-timestamp replacement, claim and burn windows being disjoint and gapless, and claim sums never exceeding the funded amount with up to 12 stakers.
    • Every division, downcast and sentinel ETH branch in Trading, plus the exported ABI files, which are byte-identical to the compiled artifacts.

    Not reached in depth. Economic manipulation of venue quotes and the registry trust model are outside my area and only noted as trust assumptions already documented by the author. No manifest exists in the tree yet, so constructor values could not be reviewed against it.

    ran onclaude · claude-fable-5-1 · 46 turns · 10m 43s · 546 in · 46K out · 1.9M cached
    submissiondee5db5b155e95a356b2fe7bc9310c0b464b15a96d77c7e66a9de416697ec9b9
    devicea18a0c6087e1362f32ade0cbf3ed270c916acf1ec0797b181c73425d1eba89e3
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
    • lowETH-funded sale reverts entirely when the ETH profit share quotes to zero IMD (boundary x precision)src/SniperVault.sol:269

      For a position bought with native ETH, _sell converts the staker share (share = profit * stakerShareBps / BPS, in wei) to IMD through _quotedSwap -> _quote. _quote (src/lib/Trading.sol:77) treats a zero quote as an invalid oracle reading and reverts InvalidQuote.

      When share is nonzero but so small that quoteExactInput(imdEthPoolId, ETH, IMD, share) truncates to 0 IMD minor units, the whole sell()/emergencySell() reverts, even though the ladder target was met and the proceeds leg would succeed.

      The seam: the guard share != 0 (line 268) is checked in ETH units, but the value that must be nonzero is the converted IMD amount, which rounds down independently. Concretely, with IMD at 6 decimals and ~3000 IMD per ETH the venue returns 3e-9 IMD units per wei, so any share below ~3.4e8 wei (profit below ~1.1e9 wei, i.e. ~1e-9 ETH) blocks the sale; with 18-decimal IMD it requires IMD to be worth more than ETH per minor unit.

      Impact is bounded: it only affects dust-sized positions (vault ETH balance on the order of 1e-7 ETH or an emergencySell that lands within a few wei of break-even), the keeper cannot clear the position via sell(), and the owner retains withdraw/withdrawAll as an exit. No funds are lost.

      Minimal fix preserving design: in _sell, when position.asset != imd, pre-check the conversion quote and treat a zero (or sub-minimum) converted amount as reward = 0 (keep the share in proceeds) instead of reverting; alternatively floor the share to the smallest amount that quotes to >= 1 IMD unit. This keeps the 'profit share goes to stakers' rule for every material sale and only drops shares that cannot be represented in IMD.

      State: BaseTest setup (vault owner OWNER, keeper KEEPER, NATIVE_POOL rate 1 wei ETH -> 10_000 NATIVE wei). Steps:

      1. OWNER: vault.withdraw(address(0), address(vault).balance - 1000) so the vault holds 1000 wei ETH; vault.setMaxSpendBps(10000); vault.setSlippageBps(0).
      2. KEEPER: vault.snipe(nativeTarget) -> position originalAmount = 10_000_000, entryCost = 1000 wei, asset = ETH.
      3. Set NATIVE_POOL quote nativeTarget->ETH to 5x (venue.setRate(NATIVE_POOL, nativeTarget, ETH, 5, 10_000)) and set IMD_POOL ETH->IMD rate to 1 IMD unit per 1e9 wei (venue.setRate(IMD_POOL, ETH, IMD, 1, 1_000_000_000)), both fresh.
      4. KEEPER: vault.sell(nativeTarget). Expected: tranche of 2_000_000 tokens sells for 1000 wei (target = ceil(2_000_000*1000/10_000_000)*5 = 1000 is met), profit = 800 wei, share = 240 wei; the sale should complete (with the staker share either converted or, if unconvertible, retained as proceeds). Actual: _quote returns quoted == 0 for 240 wei and the call reverts with Trading.InvalidQuote(); position.nextLevel stays 0 and nothing is sold. Same with OWNER: vault.emergencySell(nativeTarget) after setting the nativeTarget->ETH rate to 1005/10_000_000 (proceeds 1005, profit 5, share 1 wei) -> reverts InvalidQuote. Verified with a scratch Foundry test (test/scratch/Probe.t.sol, not kept): test_tinyEthShareBlocksLadderSell and test_tinyEthShareBlocksEmergencySell both observe the InvalidQuote revert on the current code.
    • infoExpired batch whose unclaimed remainder quotes to zero COMPANY can never be closed; rewardLiability keeps the dust foreversrc/CompanyStaking.sol:178

      burnExpired spends exactly funded - claimed through _quotedSwap, and _quote (src/lib/Trading.sol:77) reverts InvalidQuote when the venue quote is 0. After every staker has claimed, the remainder is only integer-division dust (at most one wei per claimant, e.g. 1 wei in the reproduction).

      If 1 wei of IMD buys less than one COMPANY minor unit (COMPANY priced above IMD per minor unit, or the ETH-paired two-hop path rounding to zero on either hop), the batch can never be marked burned: the batch.burned = true write is in the same transaction as the swap, so the revert undoes it.

      Consequences are accounting-only: rewardLiability and the frontend's burn history permanently carry the dust, totalImdBurned never includes it, and the dust IMD is stranded (no sweep exists by design). README and docs/SECURITY.md item 5 describe sub-unit output as 'retryable', but a batch whose dust can never quote above zero is not retryable in practice.

      Possible minimal fix (design decision for the author): close the batch without a swap when the quote for amount is zero, and either leave the dust unaccounted or add it to totalImdBurned-equivalent dust counter; carrying it into the next funded batch would change the economics slightly and should be an explicit choice.

      State: BaseTest setup.

      Steps: (1) ALICE stake(1 ether), BOB stake(2 ether) at START.

      (2) notifyReward(10) (10 wei IMD into batch BATCH).

      (3) warp START + 1 days; ALICE claimAll() -> 3 wei, BOB claimAll() -> 6 wei; rewardLiability == 1 wei of dust; batches[BATCH].claimed == 9.

      (4) warp START + 8 days; set COMPANY_POOL IMD->COMPANY rate to 1 COMPANY per 10 IMD wei (venue.setRate(COMPANY_POOL, imd, company, 1, 10)) so quoteExactInput(1 wei) == 0.

      (5) anyone: burnExpired(BATCH).

      Expected (per README): the batch closes, rewardLiability returns to 0 and burn history records it.

      Actual: reverts Trading.InvalidQuote(); batches[BATCH].burned stays false and rewardLiability stays 1 forever unless the COMPANY/IMD price later falls below 1:1 per minor unit.

      Verified with scratch test test_dustBatchUnburnableWhenQuoteZero (test/scratch/Probe.t.sol, not kept).

  7. contracts reviewed
    #638Audit economicsClaude6 findings · 1 medium
    afterBuild contract project

    The economics review is complete. Findings are in .imd-findings.json at the repository root with six findings and a coverage row for all 25 entry points plus five invariants. The only other change made during the review, a scratch test directory, has been removed, so the working tree is clean apart from the findings file.

    What I found

    No critical or high defects. The vault and staking accounting held under every invariant I traced: IMD balance versus reward liability, COMPANY balance versus total staked, per-snipe spend and supply caps, once-per-level ladder execution, proportional cost basis after partial owner withdrawals, and claim/burn window consistency. Existing tests pass. All six findings were reproduced numerically with scratch Foundry tests before being written up.

    • Medium. Expired IMD in CompanyStaking has one exit, burnExpired, which requires a working registry and venue. The deliverable contains no implementation of either, the constructor cannot detect a non-conforming address, and no launch.json exists yet. A wrong address permanently locks every unclaimed batch. The manifest stage needs accepted adapter source or verified canonical addresses before it can fill those six constructor arguments.
    • Low. Rewards are split over the whole UTC day, so a whale who stakes after seeing a funding event takes about 91 percent of that batch in my scenario. This matches the brief's wording and is reported as an incentive defect for the author to accept or adjust.
    • Low. An ETH-funded take-profit sale reverts when the unrelated IMD/ETH conversion quote is stale. Only the owner can unblock it, by zeroing the staker share.
    • Low. The burn tolerates 10 percent below quote on each hop. On the ETH-paired route that compounds to a 19 percent accepted shortfall, which a sandwich of the permissionless burn can capture.
    • Info. Two documented trust assumptions recorded for the judge: the owner can exit positions by withdrawal without paying stakers, and the per-snipe spend cap is not aggregate, so a compromised browser keeper can route most of the vault into attacker-created launches over many calls.

    Coverage

    Every listed entry point has a row. The manifest row is marked unreached because no launch.json is present. No proofs were attached since no finding reached high severity.

    ran onclaude · claude-fable-5-1 · 27 turns · 11m 55s · 418 in · 47.9K out · 1.3M cached
    submissionb6e7ddce7025897a6a177b59c865b2ee2795a299b727fc477fce6b91d7149372
    device47470357c74e4755918186564b94c9a4edc8366efed40d64a90ab0c5d2961c7b
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
    • mediumburnExpired is the only exit for expired IMD and depends on registry/venue implementations that do not exist in the deliverable; a non-conforming constructor address locks every expired batch foreversrc/CompanyStaking.sol:175

      Economic Security / dependency failure. Unclaimed IMD in CompanyStaking has exactly one outflow after the 7-day window: burnExpired, which must (a) resolve COMPANY through registry.getLaunch with origin == launchFactory and an IMD/ETH pair, then (b) execute one or two venue swaps with a fresh quote. There is no fallback, sweep or extension (by design: 'No caller can redirect the money or clear an unpaid liability').

      The constructor (src/lib/Trading.sol:42-44) only checks the six integration arguments are nonzero; it does not check code presence or ABI conformance, and docs/DEPLOYMENT.md states every one of CompanyStaking's constructor arguments 1-6 (imd_, registry_, launchFactory_, venue_, imdEthPoolId_, chainId_) is 'not supplied / unverified' and that no ILaunchRegistry or ITradingVenue implementation exists outside test/mocks. There is also no launch.json in the tree yet.

      Consequence for the manifest stage: there is currently no accepted source or verified canonical address that can legitimately fill those arguments.

      If the manifest supplies any address whose getLaunch does not report COMPANY as a launchFactory launch, or whose quote/swap ABI differs from ITradingVenue, every reward batch that is not fully claimed within 7 days (including every batch funded while nobody is staked, which is the default state at launch) becomes permanently unrecoverable IMD inside CompanyStaking, and SniperVault.snipe/sell/emergencySell revert as well.

      The vault owner can still withdraw vault funds, but expired staking IMD cannot be recovered by anyone. Needed evidence before the manifest is written: accepted, independently reviewed source (or verified on-chain code + ABI match via cast) for the registry and venue adapters that actually report the factory-created COMPANY pool and quote with real observation timestamps, plus the concrete IMD address, IMD/ETH pool id and chain id.

      This is a constructor-input/integration gap, not a request for extra manifest fields.

      Scratch test test/scratch/Dep.t.sol (run: forge test --match-path test/scratch/Dep.t.sol).

      Case A: deploy CompanyStaking(company, imd, registry, FACTORY, venue_=0x1234 (no code), IMD_POOL, chainid) -> constructor succeeds. notifyReward(1000e18) on day D with zero stakers. warp to (D+8)*86400. burnExpired(D) reverts (call to codeless address). claim(D) reverts BatchNotClaimable. imd.balanceOf(staking) == 1000e18 and rewardLiability == 1000e18 forever.

      Case B: working MockVenue but registry has no entry for COMPANY -> burnExpired(D) reverts InvalidLaunch, same lock.

      Expected: a launch whose manifest inputs are accepted should be able to burn expired rewards; actual: no accepted implementation exists to point the constructor at, and a wrong one is undetectable at deployment and unrecoverable afterwards.

    • lowReward batches are split over the whole UTC day, so a staker who enters after seeing RewardsFunded captures most of that batch from stakers who were exposed all daysrc/CompanyStaking.sol:101

      Economic Security / misaligned incentive. notifyReward credits the batch of the current UTC day, and claimable() divides that batch by stake-seconds over the full day [id*DAY, (id+1)*DAY), including seconds after the funding event.

      Because every vault sale emits Sold/RewardsFunded on-chain, a capital-rich actor can wait for a large funding event early in the day, stake for the remaining hours, and unstake at day end, taking a share proportional to amount x remaining seconds while bearing no exposure before the reward existed. Existing stakers are diluted retroactively relative to the moment the reward was earned.

      This follows the brief's wording ('split per daily batch by time-weighted stake') and is documented in the README, so it is reported as an incentive defect for the author to accept or adjust (e.g., weight by stake-seconds up to the funding timestamp, or fund the next day's batch), not as a code error.

      Scratch test test/scratch/Econ.t.sol::test_jitStakeAfterFundingCapturesBatch.

      Day D at 00:00: ALICE stakes 1,000,000 COMPANY.

      00:10: notifyReward(10,000e18 IMD).

      00:11: WHALE stakes 10,000,000 COMPANY.

      24:00: WHALE unstakes. claimable(WHALE, D) = 9084.55 IMD, claimable(ALICE, D) = 915.45 IMD.

      Expected under 'reward earned by those staked when it was earned': ALICE 10,000 IMD, WHALE 0.

      Actual: WHALE takes 90.8% having staked for 23h49m after the event.

    • lowAn ETH-funded take-profit sale is blocked by a failure of the unrelated IMD/ETH conversion pool; only the owner can unblock, by forfeiting the staker sharesrc/SniperVault.sol:269

      Flow Gap (execution x periphery x intent). For positions bought with native ETH, _sell converts the staker share ETH->IMD through imdEthPoolId inside the same transaction as the ladder sale, and the whole sale reverts if that quote is zero/stale (>15 min) or the swap misses its minimum.

      The ladder's purpose is to lock in profit when the sniped token crosses a threshold; a stale IMD/ETH quote (an independent pool/oracle) prevents the keeper from executing the sale even though the sniped token's own quote is valid and above target. The keeper has no parameter to bypass; the owner can only recover by setStakerShareBps(0), which also forfeits the stakers' share for sales executed meanwhile, or by waiting.

      Low because it is a temporary liveness loss bounded by the oracle outage, and the README documents atomicity. A minimal fix preserving intent: accrue the ETH share into a pending balance and let a separate call convert and notifyReward later, or fall back to crediting nothing while emitting a shortfall event when conversion is unavailable.

      Scratch test test/scratch/Econ.t.sol::test_ethSaleBlockedByTinyShareConversion.

      Vault holds 10 ETH; keeper snipes nativeTarget (buys 1000e18 for 0.1 ETH).

      Price moves to 5x on the token's own pool (fresh quote).

      IMD/ETH pool quote updatedAt is set 16 minutes in the past. keeper sell(nativeTarget) reverts InvalidQuote; after owner setStakerShareBps(0) the same call succeeds (nextLevel == 1).

      Expected: the 5x tranche executes since its own quote is fresh and above target; actual: blocked by the conversion leg.

    • lowburnExpired accepts up to 10% below the reference quote on each hop, so an ETH-paired COMPANY buyback can be sandwiched for up to 19% of the burned IMD by whoever calls or front-runs the permissionlessrc/CompanyStaking.sol:178

      Economic Security / sandwich of a price-dependent permissionless operation. BURN_SLIPPAGE_BPS is a hardcoded 1000 applied per hop: minOut = quote x 0.9 on IMD->ETH and again on ETH->COMPANY (lines 180-181), so the accepted end-to-end output is 0.81 of the two-hop reference. burnExpired is callable by anyone the moment expiresAt is reached, and its timing is fully predictable from batchWindow.

      An MEV actor can move the pool price up to the tolerance just before the burn (or bundle it), let the burn buy COMPANY at the worse price, and sell back, capturing up to 10% (IMD pair) or 19% (ETH pair) of the batch's IMD value minus pool fees (~1.25% per trade). The victim is COMPANY holders (fewer tokens burned).

      If the venue's quote is a spot price rather than a TWAP, the slippage bound provides no protection at all and the attacker's extraction is limited only by pool depth. The amount per batch is the full unclaimed IMD, which at launch (zero stakers) is 100% of every batch's funding.

      Scratch test test/scratch/Econ.t.sol::test_burnPerHopSlippageCompounds.

      COMPANY registered as ETH-paired; batch D funded 1000 IMD; reference rates 1000 IMD = 1 ETH, 1 ETH = 2000 COMPANY.

      Venue executes 10% worse than quote on each hop (MockVenue.configure(9000)). burnExpired(D) succeeds and burns 1620 COMPANY instead of 2000: a 19% shortfall accepted without revert.

      Expected: a bounded single tolerance on the end-to-end output (or a deadline/caller-supplied minimum checked against the reference), actual: compounding 0.9 x 0.9.

    • infoOwner can take sniped tokens out of the vault and sell them off-contract, paying stakers nothing; withdraw retires inventory and cost basis with no profit sharesrc/SniperVault.sol:131

      Trust assumption, documented separately as the adapter requires. The brief gives OWNER withdraw(asset, amount) and withdrawAll 'including sniped tokens'. Because withdraw retires position inventory without any profit calculation, the stakers' stakerShareBps is only ever paid if the owner lets the vault execute the sale.

      The staking contract's reward stream therefore depends entirely on owner behaviour, not on code enforcement; the README states this ('Owner withdrawals ... do not earn rewards'). Not a bypass of a check, so not a defect; recorded so the judge and frontend copy do not describe the 30% share as guaranteed.

      Scratch test test/scratch/Econ.t.sol::test_partialWithdrawThenLadder shows the mechanics: snipe 1000e18 tokens for 0.1 ETH; owner withdraw(token, 900e18) -> remainingAmount 100e18, remainingCost 0.01 ETH, no notifyReward.

      Later sell of the remaining 100e18 at 5x pays 12 IMD to staking; had the owner withdrawn 1000e18, staking would receive 0 while the owner sells all 1000e18 externally at 5x.

      Expected per brief wording 'stakerShareBps of each sale's profit goes to CompanyStaking'; actual: only sales routed through the vault pay.

    • infomaxSpendBps is a per-snipe fraction of the current balance, not an aggregate cap; a compromised browser keeper can route most of the vault into attacker-created launchessrc/SniperVault.sol:200

      Trust assumption, documented separately. The keeper per the brief is a password-encrypted browser wallet, i.e. a hot key weaker than the owner's. snipe(token) accepts any token the registry attributes to launchFactory; anyone can create such launches. Each snipe spends up to maxSpendBps (default 1%) of the then-current asset balance with up to slippageBps (default 10%) tolerance, with no per-day or lifetime ceiling and no cooldown.

      A keeper-key holder can create N launches whose pools they control and call snipe on each: after 100 snipes 1 - 0.99^100 = 63% of the asset balance has been swapped into those pools at up to 10% over quote; the attacker realises the value by selling into the vault's buys / holding the pool side. Output still lands in the vault, so this is not a keeper withdrawal path (the brief's forbidden case), and the README states 'this is not a daily or aggregate spending limit'.

      Mitigations if the author wants them: a per-24h spend ceiling or minimum interval between snipes, both owner-set.

      State: vault holds 100 ETH, keeper K set, maxSpendBps 100, slippageBps 1000.

      K calls snipe(T1..T100), each Ti a registry-valid launch with pool liquidity controlled by K.

      Each call passes _launch, budget = 1% of remaining balance, swap executes up to 1.1 x quote.

      After 100 calls vault ETH ~= 36.6 ETH, the rest sits in the T_i pools as K's counter-side; owner's pause() stops further snipes only once noticed.

      Expected if an aggregate cap were intended: refusal after the cap; actual: every call succeeds.

  8. contracts tested
    #1116Write foundry testsCodex5 files changedrevised
    afterBuild contract project
    writes to
    testtest/**

    Added 16 tests covering failure paths, boundaries, arithmetic fuzzing, and three invariant campaigns totaling 49,152 random calls. All changes are under test/.

    forge build passes. forge test: 78 passed, 0 failed, 0 skipped.

    No confirmed contract defects found. Live adapter compatibility remains unverified.

    ran oncodex · gpt-6-astra · 7 turns · 11m 46s · 110.5K in · 22.4K out · 1.5M cached
    submission690b3460383ce9d4f88151ff3df85e163a5533575a1420cb53e7decfcb8bdf74
    devicea9158e223bd79f257c029b3ee1b31db6956106bb2f7bdec6d7fd4c15c6a577ad
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundle8bed4f1975fde1e63f3f29d6a132364c5b00b0f587aefb15c22bf0a7d1d452f1 · 162 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 5 files
    test/BoundaryAndAtomicity.t.soltest/CONTRIBUTOR_TESTS.mdtest/CompanyStakingInvariant.t.soltest/LaunchTokenInvariant.t.soltest/SniperVaultInvariant.t.sol
  9. contracts reviewed
    #586Audit judgeClaude8 findings · 1 highrevised
    afterBuild contract project, Write foundry tests, Manifest, Audit math, Audit permissions, Audit economics, Audit flow

    Review complete. All eight findings and all 27 coverage rows are in .imd-findings.json, every snippet verified against the tree, and no file outside the findings file and test/scratch/ was changed.

    Outcome. The launch cannot deploy as described, and the source itself has one medium liveness defect plus several low and informational items. The code's access control, accounting and reentrancy handling held up under tracing; 78 existing tests and the ABI export check pass.

    Findings kept, after reproducing each in test/scratch/Repro.t.sol:

    1. High, launch.json line 12. CompanyStaking's six integration arguments are zero placeholders and chain id "0". The Trading constructor rejects every one of them, so the factory deployment fails before SniperVault is reached. Merged with the three specialists' "no registry/venue adapter exists" findings, since that is the real gap: no reviewed implementation of either port exists anywhere in the tree, and a wrong substitute strands every expired reward batch forever because CompanyStaking has no rescue path.
    2. Medium, src/SniperVault.sol:269. For ETH-funded positions, sell and emergencySell revert whenever the unrelated IMD/ETH conversion cannot be quoted, either stale beyond 15 minutes or a share that rounds to zero IMD. Four specialists reported facets of this; merged into one. The keeper has no bypass and the owner's only workaround forfeits the stakers' share.
    3. Low, src/CompanyStaking.sol:180. Claim dust that quotes to zero output can never be burned, so those batches stay open forever and the website keeper retries them indefinitely. The README calls this "retryable" but no retry can succeed.
    4. Low, src/CompanyStaking.sol:181. The 10% burn tolerance compounds per hop to 19% on the ETH-paired route the manifest selects. Reproduced with 1620 of 2000 reference COMPANY burned.
    5. Low, src/CompanyStaking.sol:101. Whole-day batch splitting lets a staker who enters after seeing the funding event capture most of that batch. Follows the brief's wording, so flagged for the author to accept or adjust.
    6. Low, docs/dependency-sha256.txt:46. This is new from my own pass. The dependency hash list the docs tell reviewers to verify fails on 10 of 47 files, including the production SafeERC20, Math and Address files, and never matched even in the commit that introduced it. The code reads as genuine OpenZeppelin v5.1.0 logic, apparently reformatted, but the sandbox refused GitHub so upstream identity stays unverified.
    7. Info. Two merged trust assumptions: the stakers' share is a live owner setting with no floor and no snapshot, and maxSpendBps is per-snipe rather than aggregate.

    Dropped or demoted. Nothing from the specialists failed to reproduce. The ETH-share dust case was folded into finding 2 rather than kept separately, and the two "expired dust" reports were merged into finding 3.

    Coverage. All 25 entry points are answered, none unreached, plus two invariant rows. No proof files were attached: the only high finding is in the manifest, where no Foundry test can fail now and pass after the fix.

    ran onclaude · claude-fable-5-1 · 40 turns · 10m 19s · 546 in · 40.7K out · 2.1M cached
    submission4ce171904778afe4e296a4f8752c4170f8f651a346ccb35f020cb08c586b7d41
    device4ebee02fc506f27c1b0fadc62728da6e6bb08e89eb86b6d3f22e7fbf1df539ab
    started fromc4d7331afc0de0700667903d63f02514d04af727
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060, 4a40d5d27d39d2cb7cbe86b03118e2eaced166a089004db234cec719f01d823c, f5a671aa6cfa379632fe666f8926b2fed766b03cbd71883e051839ca43b3079f
    changed · 0 filesnothing
    • highlaunch.json fills CompanyStaking constructor args 1-6 with zero placeholders that the constructor rejects, and no reviewed registry/venue implementation exists to replace themlaunch.json:12

      Merged from audit_flow #1, audit_permissions #1 and audit_economics #1, now concrete because launch.json is in the tree. CompanyStaking's constructor (src/lib/Trading.sol:41-45) reverts InvalidConfiguration when imd_, registry_, launchFactory_ or venue_ is address(0), imdEthPoolId_ is bytes32(0) or chainId_ is 0, and WrongChain when chainId_ != block.chainid.

      The manifest supplies exactly those values, so the factory's CREATE2 of CompanyStaking fails ('project constructor failed' in the protected harness) and SniperVault, which reads its settings from the staking address, cannot deploy either. The notes admit this is a draft, but notes carry no deployment authority; the manifest as written describes a launch that cannot be instantiated.

      The underlying gap is not a missing manifest field: brief item 3 (origin and pair check) and item 5 (COMPANY buyback) are delegated to two immutable ports, ILaunchRegistry.getLaunch and ITradingVenue quote/swap, for which no implementation exists in src/ (only test/mocks), docs/DEPLOYMENT.md marks all six values 'not supplied / unverified', and the constructor only checks nonzero, not code or ABI conformance.

      Any nonzero but non-conforming address deploys and then permanently disables snipe/sell/emergencySell and burnExpired. CompanyStaking has no owner, sweep or rescue, so every batch funded through the permissionless notifyReward that is not fully claimed in 7 days becomes IMD stranded forever (rewardLiability never reconciles).

      Needed before a manifest can be accepted: (a) adapter source for both ports committed, tested, listed before CompanyStaking and referenced via $contract:, or verified canonical Robinhood addresses with code and ABI evidence for getLaunch/quoteExactInput/quoteExactOutput/swapExactInput/swapExactOutput; (b) the IMD token address, IMD/ETH pool id and chain id from network data; (c) evidence that the registry reports COMPANY (created by ProjectFactory) with origin == launchFactory_, because burnExpired resolves COMPANY's pool through the same _launch check as snipe (src/CompanyStaking.sol:175) and otherwise reverts InvalidLaunch on every call.

      1. Manifest as written: new CompanyStaking(token, address(0), address(0), address(0), address(0), bytes32(0), 0) reverts Trading.InvalidConfiguration; the protected ProjectDeploymentProbe.deploy then fails 'project constructor failed' for IMD_PROJECT_CODE_0 and SniperVault never deploys. Scratch test test/scratch/Repro.t.sol::test_manifestZeroArgsRevertConstructor observes the revert.
      2. Non-conforming substitute: deploy CompanyStaking(company, imd, registry, FACTORY, venue_=0x1234 (no code), IMD_POOL, block.chainid); constructor succeeds. Anyone notifyReward(1000e18) on day D with no stakers. Warp to (D+8)*86400. burnExpired(D) reverts (call to codeless venue), claim(D) reverts BatchNotClaimable. Expected: expired IMD buys COMPANY and is sent to 0xdEaD. Actual: imd.balanceOf(staking) == 1000e18 and rewardLiability == 1000e18 forever; the same for every later batch.
      3. Registry that reports COMPANY with a factory other than launchFactory_: burnExpired(D) reverts Trading.InvalidLaunch at src/lib/Trading.sol:59 forever.
    • mediumETH-funded sell and emergencySell revert whenever the unrelated IMD/ETH staker-share conversion cannot be quoted (stale quote, or share so small it quotes to 0 IMD)src/SniperVault.sol:269

      Merged from audit_math #1, audit_flow #3, audit_permissions #2 and audit_economics #3 (same root cause).

      For a position bought with native ETH, _sell converts the stakers' profit share ETH->IMD through imdEthPoolId inside the same transaction as the token sale. _quotedSwap calls _quote (src/lib/Trading.sol:77), which reverts InvalidQuote when the IMD/ETH observation is zero, in the future, older than MAX_QUOTE_AGE (15 minutes) or when the converted amount rounds to 0 IMD minor units. The guard on line 268 (share != 0) is checked in wei, not in the IMD units that must be nonzero.

      The ladder's take-profit (5x..100x) and the owner's emergency exit therefore depend on a second pool's freshness and depth, even when the token's own pool quote is fresh and above target. The keeper (the automated path per brief item 9) has no bypass; the owner can only clear the sale by setStakerShareBps(0), forfeiting the stakers' share for that sale, or by withdrawing the raw token.

      Impact is a liveness loss on exactly the sales the ladder exists to lock in, and a broken emergency guarantee under a condition unrelated to the position; no vault funds are lost. Minimal fix preserving the design: when the conversion quote is invalid or quotes to 0, keep the share in the vault as a pending IMD-denominated liability and convert/notify it in a separate retryable step, or in the emergency path skip the conversion and emit the shortfall instead of reverting.

      Scratch test test/scratch/Repro.t.sol (BaseTest setup).

      Case A (stale): owner setSlippageBps(0), keeper snipe(nativeTarget), owner setSlippageBps(1000); warp +16 minutes; refresh only NATIVE_POOL at 5x (venue.setRate(NATIVE_POOL, nativeTarget, ETH, 5, 10_000)); IMD_POOL ETH->IMD quote is now 16 minutes old. keeper sell(nativeTarget) -> reverts Trading.InvalidQuote although quoted >= target; owner emergencySell(nativeTarget) -> reverts Trading.InvalidQuote.

      After owner setStakerShareBps(0), emergencySell succeeds and rewardLiability stays 0 (stakers' share forfeited).

      Case B (dust): owner withdraw(ETH, balance-1000), setMaxSpendBps(10000), setSlippageBps(0); keeper snipe(nativeTarget) -> entryCost 1000 wei; set nativeTarget->ETH to 5/10_000 and IMD_POOL ETH->IMD to 1 unit per 1e9 wei; keeper sell(nativeTarget): target met (1000 wei), profit 800, share 240 wei quotes to 0 IMD -> reverts Trading.InvalidQuote, nextLevel stays 0.

      Expected in both: the tranche sells and ETH proceeds land in the vault.

    • lowburnExpired can never close a batch whose unclaimed remainder quotes to zero output, so claim dust leaves rewardLiability and burn history permanently opensrc/CompanyStaking.sol:180

      Merged from audit_math #2 and audit_flow #2. burnExpired spends exactly funded - claimed through _quotedSwap, and _quote (src/lib/Trading.sol:77) reverts InvalidQuote when quoteExactInput returns 0. Claims use floor rounding, so after every staker claims a batch keeps 1..n-1 wei of IMD.

      With the ETH-paired COMPANY pool that launch.json selects (pairedCurrency 0x0), the first hop is IMD->ETH and a few wei of IMD quote to 0 ETH whenever IMD is worth less than ETH per minor unit; the IMD-paired route has the same failure when 1 wei of IMD buys less than one COMPANY unit. batch.burned is written in the same transaction as the swap, so the revert undoes it and the batch can never be marked burned: rewardLiability keeps the dust forever, totalImdBurned never includes it, the approved website keeper (brief item 9) retries burnExpired on every unburned expired batch indefinitely, and the burn-history view shows them as pending.

      README and docs/SECURITY.md item 5 call sub-unit output 'retryable', but no retry can ever succeed for these batches. Accounting-only impact; the stranded value is wei-level dust.

      Minimal fix: when amount quotes to zero output (or is below a dust threshold), mark the batch burned and account the dust without swapping, or carry it into the next funded batch (an explicit economic choice).

      Scratch test test/scratch/Repro.t.sol::test_dustBatchUnburnableEthPaired. registry.setLaunch(company, FACTORY, address(0), COMPANY_POOL) (ETH-paired, as in launch.json).

      ALICE stake(1e18), BOB stake(2e18) at START (batch 20000). notifyReward(10).

      Warp START+1 day: ALICE claimAll() -> 3, BOB claimAll() -> 6; rewardLiability == 1.

      Warp START+8 days, refresh all quotes (IMD->ETH 1/1000 so 1 wei IMD quotes to 0 ETH). burnExpired(20000) reverts Trading.InvalidQuote; still reverts at START+400 days; batches(20000).burned == false and rewardLiability == 1.

      Expected per README: the batch closes with burned == true and rewardLiability returns to 0.

      Same with an IMD-paired COMPANY when the IMD->COMPANY rate is set to 1 per 10 wei (audit_math reproduction).

    • lowburnExpired applies the fixed 10% tolerance per hop, so an ETH-paired COMPANY buyback accepts up to 19% below the reference and is sandwichable by anyone timing the permissionless callsrc/CompanyStaking.sol:181

      From audit_economics #4, reproduced.

      BURN_SLIPPAGE_BPS = 1000 is applied independently on IMD->ETH (line 180) and ETH->COMPANY (line 181): the second hop's minimum is 90% of a fresh quote on whatever the first hop actually delivered, so the accepted end-to-end output is 0.81 of the two-hop reference. burnExpired is callable by anyone from a timestamp predictable from batchWindow, and the amount (the full unclaimed IMD, which is 100% of every batch funded while nobody is staked) is public.

      An MEV actor can move each pool to the tolerance edge, let the burn buy at the worse price, and sell back, extracting up to ~10% (IMD pair) or ~19% (ETH pair) of the batch minus pool fees; fewer COMPANY are burned. docs/SECURITY.md item 5 discloses the compounding but the launch configuration selects the ETH pair.

      If the venue's quote is a spot price rather than a reference price, the bound gives no protection at all; that is the documented venue trust assumption, not this finding.

      Fix: bound the end-to-end output once (compute the two-hop reference and check the final COMPANY delta against one tolerance), or lower the per-hop allowance.

      Scratch test test/scratch/Repro.t.sol::test_burnPerHopSlippageCompounds. registry.setLaunch(company, FACTORY, address(0), COMPANY_POOL); notifyReward(1000e18) on batch 20000 with no stakers; warp START+8 days; refresh quotes (1000 IMD = 1 ETH, 1 ETH = 2000 COMPANY); venue.configure(9000, false, 0) so each hop executes 10% worse than its quote. burnExpired(20000) succeeds and sends 1620e18 COMPANY to 0xdEaD. Expected with a single 10% tolerance: revert or at least 1800e18; actual: 1620e18 accepted (19% shortfall).

    • lowReward batches are split over the whole UTC day, so a staker who enters after seeing RewardsFunded captures most of that batch from stakers who were exposed before the salesrc/CompanyStaking.sol:101

      From audit_economics #2, reproduced. notifyReward credits the batch of the current UTC day and claimable() divides it by stake-seconds over the full day [id*DAY, (id+1)*DAY), including seconds after the funding event.

      Every vault sale emits Sold/RewardsFunded on-chain, so a capital-rich actor can wait for a large funding early in the day, stake for the remaining hours (or back-run the sale in the same block), and unstake at day end, taking a share proportional to amount x remaining seconds with no exposure before the reward existed. Existing stakers are diluted retroactively.

      This follows the brief's wording ('split per daily batch by time-weighted stake') and the README states it, so it is reported as an incentive defect for the author to accept or adjust (weight stake-seconds only up to the funding timestamp, or fund the next day's batch) rather than a code error.

      Scratch test test/scratch/Repro.t.sol::test_jitStakeAfterFundingCapturesBatch.

      Day D at 00:00: ALICE stakes 100,000 COMPANY.

      00:10: notifyReward(10,000e18).

      00:11: WHALE stakes 10,000,000 COMPANY.

      At D+1 00:00: claimable(WHALE, D) > 9,000e18 and claimable(ALICE, D) < 1,000e18 (about 9,084 vs 915).

      Expected under 'reward earned by those staked when it was earned': ALICE 10,000e18, WHALE 0.

    • lowdocs/dependency-sha256.txt does not match 10 vendored dependency files, including SafeERC20, Math and Address used by the production contracts; the documented provenance check failsdocs/dependency-sha256.txt:46

      docs/DEPENDENCIES.md says 'No dependency code is modified' and tells reviewers to verify with sha256sum -c docs/dependency-sha256.txt; docs/SECURITY.md lists that command among 'Successful local commands'.

      On the tree as committed the check fails for 10 of 47 files: lib/openzeppelin-contracts/contracts/utils/math/Math.sol, token/ERC20/utils/SafeERC20.sol, utils/Address.sol, and seven forge-std files (StdAssertions, StdJson, StdToml, Vm, console, interfaces/IERC7540, interfaces/IMulticall3). The recorded hashes never matched: the same three OZ files hash differently at commit 796a716, the commit that introduced the hash list.

      Math.mulDiv, SafeERC20.forceApprove/_callOptionalReturn and the ReentrancyGuard (which passes) read as genuine OpenZeppelin v5.1.0 logic and the differences look like reformatting (for example Address.sol line 38 '(bool success,) =' vs upstream '(bool success, ) ='), but the review sandbox refused github.com, so upstream identity could not be confirmed here.

      Because the attestation and admission stages bind source and dependency identity, a provenance record that fails on its own tree is a defect to fix before publication: either restore the exact upstream files or regenerate the hash list from the files actually vendored and verify them against the tagged archives. Not a code vulnerability.

      In the repository root run: sha256sum -c docs/dependency-sha256.txt.

      Expected (per docs/DEPENDENCIES.md and docs/SECURITY.md): all 47 lines OK.

      Actual: 37 OK, 10 FAILED, including the three OpenZeppelin files above. git show 796a716:lib/openzeppelin-contracts/contracts/utils/math/Math.sol | sha256sum gives 89e69533... while line 46 records 68cf79a6..., so the mismatch predates any later commit.

    • infoTrust assumption: the stakers' profit share is not a code commitment; the owner can zero it before any sale (live read, no floor) or withdraw position tokens and sell them outside the vaultsrc/SniperVault.sol:266

      Merged from audit_permissions #3 and audit_economics #5; documented as an intended owner power, not a defect. _sell reads the live stakerShareBps rather than a value snapshotted at snipe (unlike the ladder, which snipe copies into the Position), and setStakerShareBps accepts 0 (brief: default 3000, max 5000, no minimum). withdraw/withdrawAll retire position inventory and cost basis without any profit calculation.

      So the 'stakerShareBps of each sale's profit, as IMD, goes to CompanyStaking' rule in brief item 4 holds only for sales the owner lets the vault execute at a nonzero share; the README discloses both. If the requester wants a credible commitment, snapshot the share into the Position at snipe or enforce a nonzero minimum.

      BaseTest: ALICE stakes 100,000 COMPANY; owner setSlippageBps(0); keeper snipe(target); price 5x.

      Owner in one block: setStakerShareBps(0) then sell(target) -> staking.totalRewardsFunded() == 0, position.proceeds > 0.

      Alternatively owner withdraw(target, originalAmount) -> remainingAmount 0, no notifyReward, tokens sold elsewhere.

      Expected by a staker reading the brief: 30% of tranche profit funded; actual: 0.

    • infoTrust assumption: maxSpendBps bounds each snipe against the current balance only; a compromised keeper key can route most of the vault into many registry-valid launchessrc/SniperVault.sol:200

      Merged from audit_permissions #4 and audit_economics #6; documented as an intended keeper power with the README's own disclosure ('this is not a daily or aggregate spending limit'). The keeper cannot withdraw, choose a route, recipient or amount, but it chooses the token and timing, and anyone can create registry-recognised launches.

      With a browser-held keeper key (brief item 9), a key holder can snipe N launches whose pools they control: after N snipes about 1 - (1 - maxSpendBps/10000)^N of the asset balance has been swapped into those pools at up to slippageBps over quote (63% after 100 snipes at the default 100 bps). Output still lands in the vault, so this is not a keeper withdrawal path. Owner controls are reactive: pause, setKeeper(0), withdraw.

      If a hard bound is wanted, add an owner-set daily or aggregate keeper spend ceiling or a minimum interval between snipes.

      BaseTest: vault holds 1000 IMD; owner setMaxSpendBps(1000) to shorten the loop.

      Register 20 distinct tokens as FACTORY launches paired with IMD with fresh quotes; keeper snipe(token_i) for i = 1..20: every call succeeds and about 85% of the initial IMD balance has left the vault into the 20 pools.

      Expected if 'at most maxSpendBps of the balance' were aggregate: refusal after 10%; actual: each call re-bases on the then-current balance.

  10. contracts updated
    #309Build contract projectCodex14 files changed

    Fixed ETH-sale quote failures, burn-dust closure, compounded slippage, and dependency checksums. Preserved the three approved economic behaviors.

    All seven verdicts are recorded in .imd-responses.json. Updated ABIs and operational documentation cover reward retries and permanently retained burn dust.

    Validation passed: forge build, all 84 tests, offline tests with an empty environment, formatting, ABI checks, and all 47 dependency checksums.

    ran oncodex · gpt-6-astra · 6 turns · 11m 34s · 104K in · 22.7K out · 1.3M cached
    submissiondbb2798dec080dc4264c7a501bf4aca61b16ca5b56b82c012f1d5bc26f95a2e8
    deviceeaa98ad0794d4f7600fe2da071ebf65e46f4c0f989bd53e39cff0d2158507704
    started from796a7166e668cc1c8546c2072cc34f0e8bde0446
    bundle8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060 · 162 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 14 files
    README.mddocs/ABI.mddocs/DEPENDENCIES.mddocs/DEPLOYMENT.mddocs/SECURITY.mddocs/abi/CompanyStaking.jsondocs/abi/SniperVault.jsondocs/dependency-sha256.txtsrc/CompanyStaking.solsrc/SniperVault.solsrc/interfaces/ITradingVenue.solsrc/lib/Trading.soltest/ApprovedEconomics.t.soltest/Revision.t.sol
  11. contracts updated
    #148ManifestCodexruntime erroron the agent's machine: the task produced no changes; the agent's last message was: Blocked by the execution environment: repository …retried on #737 (Codex)
    afterBuild contract project
    writes to
    launch.json

    the task produced no changes; the agent's last message was: Blocked by the execution environment: repository reads, forge build, and forge test all failed before starting with bwrap: setting up uid map: Permission denied.

    Writing .imd-responses.json also failed. No changes were made; the finding remains unresolved. The sandbox must be repaired before this revision can proceed.

    ran oncodex · gpt-6-astra · 3 turns · 1m 15s · 9.3K in · 1.7K out · 84.5K cached
    submission538ab3d1927a88e518e3e31f720a1a455b65ca7a5e26904740101268df4cc70c
    device820ba900755d37ad9b1686fdd6812da9b3b6d536dbaba137b17890aad597e36f
    started froma023ca16c27e0591b652fa495862cfc3878dde4b
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
  12. contracts updated
    #737ManifestCodexno change0 files changedrevised
    afterBuild contract project
    writes to
    launch.json

    Confirmed the finding; it remains unresolved. Six verified deployment inputs and compatible registry/venue evidence are missing, so launch.json is unchanged.

    Recorded the required response in .imd-responses.json, disputing repairability within the assigned scope—not the defect.

    forge build succeeded. All 88 tests passed, including four reproductions confirming the failures.

    ran oncodex · gpt-6-astra · 6 turns · 4m 31s · 47.5K in · 8K out · 496.3K cached
    submission7a485b7830e357f42a888157e0ba0d443bb2a3b4a4011fac7e8282c4ff4787ec
    device6d41a24119881b3484441ec7de1b55479a9c14932b7d0e129c79f62b98ec93e8
    started from3b1c70e568447097348eddb60cd1d45092f24e86
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
  13. contracts updated
    #683Write foundry testsCodex3 files changed
    afterBuild contract project
    writes to
    testtest/**

    Extended the existing invariant tests for deferred ETH reserves, failed retries, stale quotes, and dust accounting. Added two deterministic regression tests. All changes are under test/.

    Validation: forge build passed; forge test passed 102 tests, with zero failures or skips.

    Live Robinhood integration remains unverified; tests use local mocks.

    ran oncodex · gpt-6-astra · 6 turns · 5m 19s · 114.1K in · 9.4K out · 1.3M cached
    submission0e7f21c82522a4841aefcc736814967d18d5856848e2105b9642b56bbbc45b33
    device42dc54b22317eb8178422a6c839e11256b13030b4dfc702832024c1667ad3db1
    started from1749e82c236137c31f2278dfacd53a8c62ed361e
    bundle4a40d5d27d39d2cb7cbe86b03118e2eaced166a089004db234cec719f01d823c · 177 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 3 files
    test/CONTRIBUTOR_TESTS.mdtest/CompanyStakingInvariant.t.soltest/SniperVaultInvariant.t.sol
  14. contracts reviewed
    #801Audit judgeClaudeclone failedon the agent's machine: dependency did not apply: fatal: transport 'file' not allowedretried on #260 (Claude)

    dependency did not apply: fatal: transport 'file' not allowed

    ran onclaude · 0s
    submission7f99aec7e567f638586cd4a53810d9634048e0fb2d8f529717571747e9522b5e
    device4ca9ed4f0937da89830a0ebc4138194d204c23116ac7ce5bf6be3985f50f0dc3
    started from0243d7da4a4337ae8b16bcdf15bb4ead736fd68f
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060, 4a40d5d27d39d2cb7cbe86b03118e2eaced166a089004db234cec719f01d823c, f5a671aa6cfa379632fe666f8926b2fed766b03cbd71883e051839ca43b3079f
    changed · 0 filesnothing
    #260Claude5 findings · 1 highrevised
    afterBuild contract project, Write foundry tests, Manifest, Audit math, Audit permissions, Audit economics, Audit flow

    Review complete. The findings file is written at .imd-findings.json with 5 entries and a 30-row coverage record, and the scratch tests were removed so the tree is otherwise unchanged.

    Settlement of my round-1 findings

    • Still open, high: manifest zero placeholders. launch.json is unchanged. Lines 14 to 19 still pass six zeros to CompanyStaking, and the constructor in src/lib/Trading.sol rejects them. The author concurs it is a blocker and disputes only that a manifest-only assignment can fix it, which I agree with. It needs adapter source for the registry and venue ports (or verified canonical addresses), the IMD address, pool id and chain id, and evidence the registry reports COMPANY with the factory as origin. No Foundry proof is attached because a test that passes once the manifest is fixed would have to read launch.json, and foundry.toml grants no file permissions.
    • Fixed, medium: ETH-funded sales blocked by the IMD/ETH quote. Reproduced the stale and dust cases on the new code. Both sales complete, the share is booked as reserved ETH, the owner cannot withdraw it, a stale retry rolls back cleanly, and a fresh retry funds staking.
    • Fixed, low: dust batches unburnable. Both the ETH-paired and IMD-paired routes now close with the wei retained in a separate dust total.
    • Fixed, low: per-hop slippage compounding. The 0.9 x 0.9 case now reverts atomically, and 0.95 x 0.95 burns 1805e18 as expected.
    • Fixed, low: dependency hashes. The checksum file passes for all 47 files. The network was reachable this round, so I fetched the OpenZeppelin v5.1.0 and forge-std v1.9.7 archives: 37 files are byte-identical and the other 10 equal forge-fmt output of upstream, exactly as the author documented.
    • Accepted as approved design: whole-day batch split, zeroable staker share, per-snipe keeper cap. All three reproduce in the author's own tests, match the brief's wording, and are now disclosed in the security doc. Recorded as info trust assumptions, not defects.

    One new observation, info only. The dust fix retires a batch of any size on a single fresh zero-output quote, with no size bound or second probe. A conforming reference-price venue cannot produce that for a large batch, and the interface now forbids it, but ordinary DEX quoters do return zero with a current timestamp for an empty pool. Whoever supplies the venue for the open finding must verify this property. A cheap guard would be to retire dust only when a scaled probe amount still quotes nonzero.

    Other checks run: full suite passes (102 tests), the ABI export check passes, manifest notes and argument lengths are within schema limits, and I traced the new reserve accounting to confirm the vault's ETH balance can never fall below the pending reward total.

    ran onclaude · claude-fable-5-1 · 22 turns · 6m 24s · 322 in · 26K out · 1.1M cached
    submission92f53f7327707736548e5f4d9811fff04efdd30de7a662f5cd3f4b097ed09f16
    device6b37e4ab6524670535ab5ca4790833b288ea8c4f498948e8435b4062d7544812
    started from6112baed5b7298715c289c28095beea341d57d3d
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060, 4a40d5d27d39d2cb7cbe86b03118e2eaced166a089004db234cec719f01d823c, f5a671aa6cfa379632fe666f8926b2fed766b03cbd71883e051839ca43b3079f
    changed · 0 filesnothing
    • highlaunch.json still fills CompanyStaking constructor args 1-6 with zero placeholders that the constructor rejects; no reviewed registry/venue implementation or network values exist to replace them (unchlaunch.json:14

      Settlement of round-1 finding d7ba5801 (merged from audit_flow #1, audit_permissions #1, audit_economics #1). The author disputed only that it can be repaired inside a manifest-only assignment, and left launch.json unchanged; they agree it is a deployment blocker.

      Re-verified on the current tree: launch.json lines 14-19 supply address(0) x4, bytes32(0) and 0 for imd_, registry_, launchFactory_, venue_, imdEthPoolId_ and chainId_. src/lib/Trading.sol:41-45 reverts InvalidConfiguration on any zero and WrongChain on a chain-id mismatch, so the factory's CREATE2 of CompanyStaking fails and SniperVault (which reads its settings from the staking address in its constructor, src/SniperVault.sol:82-90) cannot deploy either.

      Notes carry no deployment authority. The underlying gap is unchanged: brief items 3 and 5 are delegated to two immutable ports (ILaunchRegistry.getLaunch, ITradingVenue quote/swap) with no implementation in src/ (only test/mocks), docs/DEPLOYMENT.md still marks all six values 'not supplied / unverified', no network.json is present under .imd/reads, and the constructor checks only nonzero, not code or ABI conformance.

      A nonzero but non-conforming address deploys and then permanently disables snipe/sell/emergencySell and burnExpired; CompanyStaking has no owner, sweep or rescue, so every batch funded through the permissionless notifyReward that is not fully claimed in 7 days is IMD stranded forever.

      What is needed before a manifest can be accepted is unchanged: (a) adapter source for both ports committed in a source-producing assignment, tested, listed before CompanyStaking and referenced via $contract:, or verified canonical Robinhood addresses with code and ABI evidence for getLaunch/quoteExactInput/quoteExactOutput/swapExactInput/swapExactOutput; (b) the IMD token address, IMD/ETH pool id and chain id from network data; (c) evidence that the registry reports COMPANY (created by ProjectFactory) with origin == launchFactory_, since burnExpired resolves COMPANY's pool through the same _launch check as snipe (src/CompanyStaking.sol:177).

      This is a service/integration gap, not a request for extra manifest fields. No Foundry proof is attached: a test that passes once the manifest is fixed would have to read launch.json, and foundry.toml grants no fs_permissions (a configuration file reviewers may not change); the constructor-level reproduction below is deterministic without it.

      1. Manifest as written: new CompanyStaking(token, address(0), address(0), address(0), address(0), bytes32(0), 0) reverts Trading.InvalidConfiguration (scratch test SettleTest.test_manifestZeroArgsStillRevert, run this round, passes on the current tree). The protected ProjectDeploymentProbe.deploy therefore fails 'project constructor failed' for IMD_PROJECT_CODE_0 and SniperVault never deploys; the author's own test/scratch reproduction reported the same.
      2. Non-conforming substitute: deploy CompanyStaking(company, imd, registry, FACTORY, venue_=0x1234 (no code), IMD_POOL, block.chainid); constructor succeeds. Anyone notifyReward(1000e18) on day D with no stakers; warp to (D+8)*86400; burnExpired(D) reverts (call to codeless venue) and claim(D) reverts BatchNotClaimable; imd.balanceOf(staking) == 1000e18 and rewardLiability == 1000e18 forever.
      3. Registry reporting COMPANY with a factory other than launchFactory_: burnExpired(D) reverts Trading.InvalidLaunch at src/lib/Trading.sol:59-61 on every call. Expected: a manifest whose constructor arguments instantiate a working, independently reviewed registry and venue on the target chain. Actual: a manifest that cannot be instantiated, and no accepted source or verified address that could fill it.
    • infoTrust assumption introduced by the dust fix: burnExpired retires a batch of any size permanently on a single fresh zero-output quote, so the venue adapter must never answer (0, fresh) for missing liqusrc/CompanyStaking.sol:188

      Not a defect in the delivered code; recorded so the adapter reviewer sees it. The fix for round-1 finding 826ff215 (dust batches unburnable) closes a batch without swapping whenever the fresh reference quote for the whole unclaimed amount is zero (src/CompanyStaking.sol:188-195), adding the IMD to totalImdDust with no rescue or later spending path. There is no size bound or second probe: a 1000e18 IMD batch is retired exactly like a 1-wei one.

      A conforming reference-price adapter cannot return zero for a large amount unless COMPANY is astronomically expensive, and src/interfaces/ITradingVenue.sol:7-8 now requires missing pricing to revert or carry an invalid timestamp. But ordinary DEX quoters (for example a Uniswap-style quoter on a pool with no liquidity in range) return 0 with the current timestamp rather than reverting, which docs/DEPLOYMENT.md already warns are incompatible.

      Whoever supplies the venue for finding 1 must verify this property; a cheap in-contract guard that preserves the chosen policy would be to retire as dust only when a scaled probe (for example amount * 1e6, or a fixed 1e18) also quotes nonzero, proving pricing exists while this amount is sub-unit.

      Scratch test SettleTest.test_freshZeroQuoteRetiresWholeBatch (BaseTest setup, IMD-paired COMPANY): notifyReward(1000e18) in batch 20000 with no stakers; warp START+8 days; _refreshRates(); venue.setRate(COMPANY_POOL, imd, company, 0, 1) so quoteExactInput returns (0, block.timestamp). burnExpired(20000) returns 0, totalImdDust == 1000e18, rewardLiability == 0, batches(20000).burned == true.

      Restore the rate with _refreshRates(); burnExpired(20000) reverts BatchAlreadyBurned and imd.balanceOf(staking) stays 1000e18 with no path out.

      Expected with a conforming venue: the zero quote never occurs for such an amount; actual with a non-conforming one: the entire batch is stranded instead of staying retryable.

    • infoTrust assumption (approved design, disclosed): the stakers' profit share is not a code commitment; the owner can zero stakerShareBps before any sale or withdraw position tokens and sell them outside tsrc/SniperVault.sol:292

      Settlement of round-1 info 1f9c8575 (merged from audit_permissions #3 and audit_economics #5). The author kept the live read and the no-minimum setter because brief items 2 and 4 grant exactly these powers, and README/docs/SECURITY.md item 8 now disclose the effect. Accepted as a documented owner power, not a defect.

      The deferred-reward repair does protect shares already booked as pendingRewardsEth: _withdraw reverts ReservedRewards above availableBalance (src/SniperVault.sol:136) and later setStakerShareBps changes do not touch the reserve.

      test/ApprovedEconomics.t.sol::test_reproOwnerCanZeroShareAndWithdrawInventory (passes on the current tree): owner setStakerShareBps(0) then sell(target) at 5x funds 0 IMD to staking while position.proceeds > 0; owner withdraw(target, amount) retires inventory with no notifyReward. Expected by a staker reading only brief item 4: 30% of tranche profit funded; actual: 0, as the README states.

    • infoTrust assumption (approved design, disclosed): maxSpendBps bounds each snipe against the current available balance only, so a compromised keeper key can route most of the vault into many registry-valisrc/SniperVault.sol:226

      Settlement of round-1 info c5feb9a4 (merged from audit_permissions #4 and audit_economics #6). Brief item 3 defines the cap per snipe against the current balance; the author kept it and added an explicit disclosure of cumulative keeper exposure (docs/SECURITY.md item 8). Accepted as a documented keeper power.

      The fix only changed the base from gross balance to availableBalance, so reserved reward ETH can no longer fund buys.

      test/ApprovedEconomics.t.sol::test_reproKeeperSpendIsPerPurchase (passes on the current tree): vault holds 1000 IMD, owner setMaxSpendBps(1000), 20 distinct registry-valid IMD-paired launches with fresh quotes; keeper snipe(token_i) for i = 1..20 all succeed and fewer than 150 IMD remain. Expected under an aggregate reading: refusal after 10%; actual: each call re-bases on the then-current balance, as the README states.

    • infoTrust assumption (approved design, disclosed): reward batches are split over the whole UTC day, so a staker entering after RewardsFunded captures most of that batchsrc/CompanyStaking.sol:103

      Settlement of round-1 low 658bd5f9 (from audit_economics #2). The author disputed it as a defect: brief item 5 specifies daily batches split by time-weighted stake with unrestricted entry and exit, the README defines the batch as the whole UTC day, and docs/SECURITY.md item 8 now discloses post-funding dilution.

      Changing to a funding-time snapshot or next-day funding would alter the approved economic model, so this is recorded as an accepted incentive property rather than kept as a defect.

      test/ApprovedEconomics.t.sol::test_reproPostFundingStakeSharesWholeDay (passes on the current tree): ALICE stakes at day start, notifyReward(10,000e18) at 00:10, a large stake enters at 00:11; at day end the late staker's claimable exceeds 9,000e18 and ALICE's is below 1,000e18.

  15. contracts updated
    #248ManifestCodexno change0 files changed
    afterBuild contract project
    writes to
    launch.json

    The finding reproduces and remains unresolved. Repair requires six approved constructor values and verified registry/venue implementations; launch.json is unchanged.

    Recorded the evidence and scope limitation in .imd-responses.json.

    forge build succeeded. All 88 tests passed, including four confirming the reported failures. The manifest remains undeployable.

    ran oncodex · gpt-6-astra · 5 turns · 2m 59s · 54.5K in · 5.8K out · 296.4K cached
    submissionaaf94f0d3856f45e679c0285943ceb2a7e3723b27957c8c1a7991e7a2a30e741
    device726aa3006eb6a1e1eea9477f7293686902055a65db713d69f41d2e6cac7faabd
    started fromb746c1ea5d9fd882aa133ecea62a3c49feacfa55
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060
    changed · 0 filesnothing
  16. contracts reviewed

    judge findings unresolved after 2 revisions: no revision budget left for manifest (2 revisions, 2 from the judge) — NOT FIXED: launch.json still fills CompanyStaking constructor args 1-6 with zero placeholders the constructor rejects; no reviewed registry/venue implementation or network values exist to replace them

    #11Audit judgeClaude5 findings · 1 high
    afterBuild contract project, Write foundry tests, Manifest, Audit math, Audit permissions, Audit economics, Audit flow

    Review complete. .imd-findings.json is written with 5 findings and a 29-row coverage record (all 26 entry points answered). I changed no repository files; only .imd-findings.json and the scratch test test/scratch/Settle2.t.sol are new, and git status shows nothing else.

    Settlement of my prior findings

    [high] launch.json zero placeholders — NOT FIXED, kept at high. The author's answer agrees it reproduces and disputes only that a manifest-only assignment can repair it. The tree confirms that: git diff b8a88bf HEAD -- launch.json is empty and src/ is byte-identical to what I reviewed last round (only test files changed in 09d8877). Re-reproduced with four scratch tests, all passing on this tree:

    • new CompanyStaking(token, 0, 0, 0, 0, bytes32(0), 0) → InvalidConfiguration (src/lib/Trading.sol:41-44)
    • a copy of the protected probe's deploy with the manifest's args → "project constructor failed"
    • codeless venue 0x1234 → constructs, notifyReward(1000e18), then after day+8 burnExpired reverts, claim reverts, 1000e18 IMD and rewardLiability stranded permanently
    • registry reporting COMPANY under a different factory → burnExpired reverts InvalidLaunch forever

    No Foundry proof is attached, deliberately: the fix lives in launch.json, reading it needs fs_permissions that foundry.toml doesn't grant, and a test hard-coding the zero args would keep failing after a correct fix. The needed inputs are unchanged: reviewed adapter source for both ports (or verified canonical addresses with code/ABI evidence), the IMD address / IMD-ETH pool id / chain id, and evidence the registry reports COMPANY with origin == launchFactory_.

    Four advisories (info) — carried forward unchanged as disclosed trust assumptions: whole-batch dust retirement on a fresh zero quote, owner's ability to zero stakerShareBps/withdraw inventory, per-snipe maxSpendBps, and whole-day batch dilution. Each still has a passing reproduction in test/ApprovedEconomics.t.sol or scratch.

    Specialist findings this round

    Their reports describe the pre-fix code; I verified each against the current tree:

    • Merged into finding 1: audit_flow #1, audit_permissions #1, audit_economics #1 (registry/venue gap).
    • Dropped — fixed in the current code, confirmed by scratch tests: tiny-ETH-share revert (math #1) now defers to pendingRewardsEth (240 wei in the repro); stale IMD/ETH quote blocking sell/emergencySell (flow #3, permissions #2, economics #3) now defers and anyone can fundPendingRewards; dust batches unburnable (math #2, flow #2) now close with totalImdDust == 1; per-hop slippage compounding to 19% (economics #4) — 10%-per-hop execution now reverts "slippage" and 5%-per-hop passes, because the final bound is the full-route quote.
    • Merged into the infos: permissions #3/#4, economics #2/#5/#6.
    • Slither/aderyn leads (strict equalities, uninitialized locals, zero-check on setKeeper, "locks ether"): traced, none a defect — the ETH-paired burn leaves address(staking).balance == 0 and receive() rejects non-venue senders.

    Full suite: 102 passed, 0 failed. Documented ABIs match the compiled artifacts (sha256 of normalized JSON equal for all three contracts).

    ran onclaude · claude-fable-5-1 · 21 turns · 6m 13s · 40 in · 23.5K out · 1.8M cached
    submissionab92d9cea2b0c3fb6487c9c2eab5c4085a4b6092ea77ae1f41664302482887ce
    devicec2e167266d13dcf4799e5339055687439f82b9c239967df30587a4703733486f
    started from4cf70f5eac1fcce2a282042db0ccc26ed7165619
    bundlenone
    applied on8a81d45d7f43d7c5108bac02413fa104a1c73cf74db50a9fa152c75db7495060, 4a40d5d27d39d2cb7cbe86b03118e2eaced166a089004db234cec719f01d823c, f5a671aa6cfa379632fe666f8926b2fed766b03cbd71883e051839ca43b3079f
    changed · 0 filesnothing
    • highNOT FIXED: launch.json still fills CompanyStaking constructor args 1-6 with zero placeholders the constructor rejects; no reviewed registry/venue implementation or network values exist to replace themlaunch.json:14

      Settlement of round-2 finding 899545bf (round-1 d7ba5801; merges audit_flow #1, audit_permissions #1, audit_economics #1 again this round).

      The author's answer agrees it reproduces and is a deployment blocker and disputes only that a manifest-only assignment can repair it; the tree confirms nothing changed: git diff b8a88bf HEAD -- launch.json is empty, and src/ is byte-identical to the tree I reviewed last round (only test files changed in 09d8877). launch.json lines 14-19 supply address(0) x4, bytes32(0) and "0" for imd_, registry_, launchFactory_, venue_, imdEthPoolId_ and chainId_. src/lib/Trading.sol:41-44 reverts InvalidConfiguration on any zero (and WrongChain on a chain-id mismatch), so ProjectFactory's CREATE2 of CompanyStaking fails and SniperVault, whose constructor reads its settings from the staking address (src/SniperVault.sol:82-90), cannot deploy either.

      Notes carry no deployment authority. The underlying gap is unchanged: brief items 3 and 5 are delegated to two immutable ports (ILaunchRegistry.getLaunch, ITradingVenue quote/swap) with no implementation in src/ (only test/mocks), docs/DEPLOYMENT.md still marks all six values 'not supplied / unverified', .imd/reads contains no network.json, and the constructor checks only nonzero, not code or ABI conformance.

      A nonzero but non-conforming address deploys and then permanently disables snipe/sell/emergencySell and burnExpired; CompanyStaking has no owner, sweep or rescue, so every batch funded through the permissionless notifyReward that is not fully claimed in 7 days is IMD stranded forever.

      Severity stays high: permanent breakage / stranded funds if a wrong value is supplied, and no deployment at all as written.

      What is needed is unchanged and is a source/service gap, not a manifest field: (a) adapter source for both ports committed by a source-producing assignment, tested, listed before CompanyStaking in launch.json and referenced via $contract:, or verified canonical Robinhood addresses with code and ABI evidence for getLaunch/quoteExactInput/quoteExactOutput/swapExactInput/swapExactOutput; (b) the IMD token address, IMD/ETH pool id and chain id from network data; (c) evidence that the registry reports COMPANY (created by ProjectFactory) with origin == launchFactory_, since burnExpired resolves COMPANY's pool through the same _launch check as snipe (src/CompanyStaking.sol:177).

      No Foundry proof is attached, deliberately: the fix lives in launch.json, a test that reads it needs fs_permissions that foundry.toml does not grant (a configuration file reviewers may not change), and a test hard-coding the zero arguments would keep failing after a correct fix. The constructor-level reproduction below is deterministic without it.

      Scratch file test/scratch/Settle2.t.sol (run this round, all four pass on the current tree): (1) test_F1_manifestZeroArgsRevertInvalidConfiguration: new CompanyStaking(token, address(0), address(0), address(0), address(0), bytes32(0), 0) reverts Trading.InvalidConfiguration.

      (2) test_F1_protectedProbeFailsOnManifestArgs: a copy of the protected ProjectDeploymentProbe.deploy given type(CompanyStaking).creationCode ++ abi.encode(manifest args) reverts 'project constructor failed', so IMD_PROJECT_CODE_0 fails and SniperVault never deploys.

      (3) test_F1_codelessVenueStrandsExpiredBatch: CompanyStaking(company, imd, registry, FACTORY, venue_=0x1234 (no code), IMD_POOL, block.chainid) constructs; notifyReward(1000e18) on day D with no stakers; warp (D+8)*86400; burnExpired(D) reverts (call to codeless venue), claim(D) reverts BatchNotClaimable, imd.balanceOf(staking) == 1000e18 and rewardLiability == 1000e18 with no path out.

      (4) test_F1_wrongFactoryForCompanyBlocksBurn: registry reports COMPANY with origin 0xDEAD1 != launchFactory; burnExpired(D) reverts Trading.InvalidLaunch (src/lib/Trading.sol:59-61) on every call.

      Expected: a manifest whose constructor arguments instantiate a working, independently reviewed registry and venue on the target chain.

      Actual: a manifest that cannot be instantiated, and no accepted source or verified address that could fill it.

    • infoTrust assumption (carried forward, unchanged code): burnExpired retires a batch of any size permanently on a single fresh zero-output quote, so the venue adapter must never answer (0, fresh) for missisrc/CompanyStaking.sol:188

      Settlement of round-2 info 306bb89e. Not a defect in the delivered code; the code at this line is unchanged since last round and the property is now stated as a requirement in src/interfaces/ITradingVenue.sol:7-8 and docs/DEPLOYMENT.md.

      Recorded again so whoever supplies the venue for finding 1 verifies it: a 1000e18 IMD batch is retired to totalImdDust exactly like a 1-wei one if the venue returns (0, block.timestamp) for a pool with no liquidity in range, as ordinary DEX quoters do. A cheap guard preserving the policy would be to retire as dust only when a scaled probe (e.g. 1e18) also quotes nonzero.

      The dust path itself works as intended: test/scratch/Settle2.t.sol::test_S_dustBatchIsRetired_IMDpair and _ETHpair close 1-wei remainders with burned == true, rewardLiability == 0, totalImdDust == 1 (this also closes audit_math #2 and audit_flow #2, which describe the pre-fix code).

      Round-2 scratch SettleTest.test_freshZeroQuoteRetiresWholeBatch, unchanged behaviour on this tree: notifyReward(1000e18) in batch 20000 with no stakers; warp START+8 days; venue.setRate(COMPANY_POOL, imd, company, 0, 1) so quoteExactInput returns (0, block.timestamp); burnExpired(20000) returns 0, totalImdDust == 1000e18, rewardLiability == 0, batches(20000).burned == true; a later burnExpired reverts BatchAlreadyBurned and the 1000e18 IMD has no path out. Expected with a conforming venue: never occurs for such an amount.

    • infoTrust assumption (approved design, disclosed): stakers' profit share is not a code commitment; owner can zero stakerShareBps before a sale or withdraw position tokens and sell them outside the vaultsrc/SniperVault.sol:292

      Settlement of round-2 info efa22083 (merges audit_permissions #3 and audit_economics #5 again this round). Brief items 2 and 4 grant exactly these powers (setStakerShareBps with no minimum; withdraw/withdrawAll including sniped tokens), and README / docs/SECURITY.md disclose the effect. Accepted as a documented owner power, not a defect.

      Shares already booked as pendingRewardsEth are protected: _withdraw reverts ReservedRewards above availableBalance (src/SniperVault.sol:136), confirmed this round in test/scratch/Settle2.t.sol::test_S_staleRewardQuoteDefersSellAndEmergencySell.

      test/ApprovedEconomics.t.sol::test_reproOwnerCanZeroShareAndWithdrawInventory (passes on this tree): owner setStakerShareBps(0) then sell(target) at 5x funds 0 IMD to staking while position.proceeds > 0; owner withdraw(target, amount) retires inventory with no notifyReward. Expected by a staker reading only brief item 4: 30% of tranche profit; actual: 0, as the README states.

    • infoTrust assumption (approved design, disclosed): maxSpendBps bounds each snipe against the current available balance only, so a compromised keeper key can route most of the vault into many registry-valisrc/SniperVault.sol:226

      Settlement of round-2 info 3685a9f1 (merges audit_permissions #4 and audit_economics #6 again this round). Brief item 3 defines the cap per snipe against the current balance; README and docs/SECURITY.md item 8 disclose cumulative keeper exposure. Accepted as a documented keeper power.

      The base is availableBalance, so reserved reward ETH cannot fund buys; the keeper still cannot move funds out of the vault (output lands in the vault, owner retains pause/setKeeper(0)/withdraw).

      test/ApprovedEconomics.t.sol::test_reproKeeperSpendIsPerPurchase (passes on this tree): vault holds 1000 IMD, owner setMaxSpendBps(1000), 20 distinct registry-valid IMD-paired launches with fresh quotes; keeper snipe(token_i) for i = 1..20 all succeed and fewer than 150 IMD remain. Expected under an aggregate reading: refusal after 10%; actual: each call re-bases on the then-current balance, as the README states.

    • infoTrust assumption (approved design, disclosed): reward batches are split over the whole UTC day, so a staker entering after RewardsFunded captures most of that batchsrc/CompanyStaking.sol:103

      Settlement of round-2 info 9542e698 (audit_economics #2 again this round). Brief item 5 specifies daily batches split by time-weighted stake with unrestricted entry and exit; README defines the batch as the whole UTC day and docs/SECURITY.md item 8 discloses post-funding dilution. A funding-time snapshot or next-day funding would change the approved economic model, so this stays an accepted incentive property, not a defect.

      test/ApprovedEconomics.t.sol::test_reproPostFundingStakeSharesWholeDay (passes on this tree): ALICE stakes at day start, notifyReward(10,000e18) at 00:10, a large stake enters at 00:11; at day end the late staker's claimable exceeds 9,000e18 and ALICE's is below 1,000e18.

  17. contracts publishedidentity-md-launches/launch-787-workflow-contract-stage-context/pull/1
  18. deployed
    0 contractson Robinhood Chainfindings: 1 blocking finding(s) never resolved — audit_judge: NOT FIXED: launch.json still fills CompanyStaking constructor args 1-6 with zero placeholders the constructor rejects; no reviewed registry/venue implementation or network values exist to replace them
    rebuilt
    CompanyStaking, LaunchToken (Zero Person Billion Dollar Company $COMPANY), StakeHistory, SniperVault · verifier 0.1.0 · solc 0.8.26
    gates
    5 of 7 passed
    • provenance
    • findings
    • independent review
    • bytecode
    • manifest
    • protected invariants
    • economics
    parked
    findings: 1 blocking finding(s) never resolved — audit_judge: NOT FIXED: launch.json still fills CompanyStaking constructor args 1-6 with zero placeholders the constructor rejects; no reviewed registry/venue implementation or network values exist to replace them
    proof
    commit, attestation, manifest, tree, per-contract hashes
    repository
    identity-md-launches/launch-787-workflow-contract-stage-context
    commit
    1e560dd1801c338c8636c0b10150dc51f8b0f9bc
    attestation
    fe981f9cbad7d3791da785203527eebcc44a806ea4ebd4ff981b0411af9c9a73
    manifest
    7fa64c6b048123e7c525fa043aa6120f80c16a24e38cda077a769918ed43e798
    constructor
    CompanyStaking: $token, 0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0
    constructor
    SniperVault: $owner, $contract:CompanyStaking
    tree
    22040c904ce2ee47211034fee248eb7148339efb
    compiler
    solc 0.8.26, optimizer 200 runs, reproducible
    contract
    CompanyStaking
    src/CompanyStaking.sol · 10417 bytes
    creation eeb37846df800c855ac3e14964537715886d6daa9b331adbdc72981a64c2b885
    abi 13f000b04716d210003df159ede81653fccd7d78f17fa2715b449129d6c1ef17
    metadata b296a0555e639e42aad017c8041b0292c207119d56659379263d769c1da0b014
    contract
    LaunchToken · Zero Person Billion Dollar Company $COMPANY
    src/LaunchToken.sol · 2715 bytes
    creation b985f144ad87673d9a4b4f61233b7413abef8e3b90eed797915ee4eb871acfb0
    abi 38880b8e56d42ce900f744a7908c7139632a49f1c3f33385c64ceaed29d37bee
    metadata d562d5467a5f0f3b812f1a7f2a52e903f9eae38fce066c48594176d377d647f8
    contract
    StakeHistory
    src/lib/StakeHistory.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
    metadata f71f9611f7d7440e25e1bf80bb893ae70b92c7dba8b75b026e953d96cb61d72f
    contract
    SniperVault
    src/SniperVault.sol · 15667 bytes
    creation 1ccddf186dbf89d930c6a865ef78200002604ac772bf97a5cde906ae405933df
    abi 1887d46fb34b593fb143267abbe6527ee33829a0b3cf1c90f133fecc37391a13
    metadata d4a5f4fee16f1a7148a6a41bc05b6b2edaf58e982602e4c42985c14a8a3c9bed
  19. website builtafter deployment
  20. website publishedafter the website is accepted
  21. hostedWorkflow paused. Waiting for the website build and GitHub publication.
  22. checkednot reached