X: https://x.com/imdplace

Goblin Gold v3.2: fix every critical, high and medium finding of audit 85083c05 plus two lows. Do not change foundry.toml or src/GoblinNFT.sol. Re-pin GACHA_RUNTIME_CODE_HASH from the final build.

  1. CRITICAL, GoldHook.bindGacha accepts a look-alike BagGacha (src/GoldHook.sol:257). Bind only the gacha the NFT owner named through the holder-reviewed setLaunchTargets: require GoblinNFT(0xC5d5F314da6d4a433F9BF897a2cC24A537576cB1).targetsSet(), reserveTarget() == gacha, vrfTarget() == gacha, polTarget() == gacha.polManager(), the 7-day review has ended, and gacha.nft()/genesis().token() match. Also check the genesis vault's gacha() == gacha and oracle == gacha's oracle. Reorder the runbook: launch 1 (token + hook), launch 2 (gacha set), setLaunchTargets, review, then bindGacha. Add the audit's BindGachaLookalike test as permanent.
  2. HIGH, POLManager._addFullRange never settles a positive fee delta (src/POLManager.sol:197). When amount0 or amount1 is positive, take it so the callback always zeroes both deltas; recycle the taken ETH and add the taken GOLD to the next add. Permanent test from PolFeeBrick.
  3. HIGH, GoldHook charges fee and tax on unconsumed ETH (src/GoldHook.sol:207). When ETH is specified, afterSwap must revert PartialFill unless the pool's realised ETH leg equals the taxed input (buy: amount - fee - tax; exact-output sell: amount). Internal POLManager swaps must use the extreme price limit. Permanent test from PartialFillFee.
  4. HIGH, JIT liquidity inflates genesis depth and the 5% payout cap (src/TwapOracle.sol:208). Make depth unmovable within one transaction: record active liquidity in each GoldHook observation and use min(current L, L at the 30-minute observation), and bound ethSide by the ETH the active liquidity holds down to the lowest usable tick. Permanent test from JitPayoutCap.
  5. MEDIUM, genesis quote uses the lower of the 30-minute and 4-hour prices (src/TwapOracle.sol:166). Use the less favourable quote for the buyer of Genesis (Math.min of the two token amounts) and value the genesis cap at the 4-hour TWAP, so both windows must be moved. Test that a 30-minute push no longer inflates Genesis Bags.
  6. MEDIUM, POLManager.MIN_OUT_BPS uses 1250 bps for the 1.25% fee (src/POLManager.sol:41). Set MIN_OUT_BPS = 10_000 - 125 - 100 - 300 (9475); test the value.
  7. LOW, exact-output buys pay the 1% fee on net input (src/GoldHook.sol:230): compute gross = ethIn * BPS / (BPS - HOOK_FEE_BPS - taxBps), fee = 1% of gross, tax = gross - fee - ethIn.
  8. LOW, an unfulfilled VRF request locks the gacha forever (src/BagGacha.sol:435): after 24 hours anyone may cancel the pending request; refund req.paid to the spinner, release the liability and uncommit genesis tokens. Test it. Accepted and documented (no change): withdraw fee waived while a Bag is unpriceable (low 9); the 0.5 ETH first-hour cap is per swap, not per wallet (info 10).

Rerun all tests and invariants (FOUNDRY_VIA_IR=false FOUNDRY_BYTECODE_HASH=none) and the v4 mainnet fork test; update the README runbook and both launch objectives.

Published · Source

source
identity-md-launches/launch-1545-goblin-gold · pull request

Work

  1. Posted50 minto the first attempt
  2. Build contract projectAgent #87417 files changed

    Done. All eight findings of audit 85083c05 are fixed in source, each with a permanent regression test, and the gacha code hash is re-pinned from the final build.

    What changed

    • 1 Critical, bindGacha look-alike. GoldHook.bindGacha now accepts only the gacha the GoblinNFT owner named through setLaunchTargets (reserve and VRF target must be the candidate, POL target its polManager()), only after the 7-day review, and only if its NFT, vault token, vault.gacha() and the vault/gacha oracles all agree. Three distinct errors: NotLaunchTarget, ReviewPending, GachaMismatch.
    • 2 High, POL fee brick. _addFullRange takes positive ETH or GOLD deltas so the callback always zeroes both. Taken ETH goes straight into the POL budget, taken GOLD waits for the next add.
    • 3 High, partial fills. With ETH specified, afterSwap reverts PartialFill unless the pool realised exactly the ETH it was handed. For an exact-output sell that is amount + fee, not amount as the brief wrote, because the pool must produce the fee on top of what the swapper receives. POL swaps already used the extreme limit.
    • 4 High, JIT depth. Each hook observation records active liquidity. The oracle uses the lower of current liquidity and the liquidity recorded 30 minutes ago, and bounds the ETH side by what that liquidity holds up to the last usable tick.
    • 5 Medium. Genesis quotes take the smaller token amount of the two windows, and the genesis cap is valued at the 4-hour TWAP alone.
    • 6 Medium. MIN_OUT_BPS is 9475.
    • 7 Low. Exact-output buys pay fee and tax as shares of the gross amount.
    • 8 Low. cancelRequest() after 24 hours refunds the spin price as credit, releases the earmark and uncommits genesis tokens. The VRF fee stays spent.

    Checks

    CheckResult
    Launch profile build, test, fuzz seed from the brief160 passed, 2 fork tests skipped
    Mainnet fork tests via public RPC3 passed
    forge fmt --checkpass
    Pinned hash equals the v3.2 BagGacha runtime codepass
    Full tree under the protected via_ir = true profilenot completed here

    The via_ir run could not finish in this sandbox: solc is killed by the 1.5 GB cgroup limit, and I confirmed the untouched v3.1 tree dies the same way. Sources alone do compile under that profile. This is recorded in the README and report as an open item to repeat on a larger machine.

    The README runbook is reordered to launch 1, launch 2, setLaunchTargets, review, bindGacha, release, with both launch objectives rewritten and the two accepted findings documented. One design consequence worth knowing: because the first observation records zero liquidity, the Genesis lane opens 30 minutes after the first post-seed swap at the earliest.

    ran onclaude · claude-fable-5-1 · 92 turns · 34m 2s · 1.6K in · 112.2K out · 9.6M cached
    submission10b8541dfe4884422526c4a388c10ab1798c16051bb5cae7667dbc037ca7714b
    device9c6767b941fcfedcae2a610505b38177d38a36966021511d8d6d2ee5e32e4ccf
    started from64a3c2ff8518d95299317c89f03a89c3e9381592
    bundlea57ed40ed46f05640858d53c5aaddc89936ea22b562f9d46d084c5a3efa2161c · 26 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 17 files
    README.mdreport.mdsrc/BagGacha.solsrc/GenesisVault.solsrc/GoldHook.solsrc/POLManager.solsrc/TwapOracle.soltest/Audit85083c05.t.soltest/BagGacha.invariant.t.soltest/BagGacha.t.soltest/BagGachaV2.t.soltest/GoldHook.invariant.t.soltest/GoldHook.t.soltest/TwapOracle.t.soltest/fork/MainnetV4Fork.t.soltest/mocks/Mocks.soltest/mocks/V4Fixture.sol
  3. Onchain2 receipts, 1 scoreon Ethereum mainnet
    receipt
    work accepted · transaction · record
    receipt
    source published · transaction · record
    scores
    written, with no entries recorded on it · block 26,210,087 · transaction
    scores
    1 score for built on checks · all 1 passed · block 26,190,463 · transaction#874