Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Uniswap V2

Showcase port of Uniswap V2 Core (UniswapV2Factory, UniswapV2Pair, test ERC20) under examples/uniswap-v2/ in cambrian-lang. First in-repo exercise of two-field identity CREATE2, deep phased routes with cross-contract var capture, and EIP-2612 permit via evm::.

Layout

examples/uniswap-v2/
  project.yaml              # target: evm, via_ir
  project.lean.yaml
  shared.cam                # imported pure helpers (min/max/mul_div)
  ERC20.cam                 (+ .test / .fuzz / .invariant)
  UniswapV2Pair.cam         (+ .test / .fuzz / .invariant)
  UniswapV2Factory.cam      (+ .test / .invariant)
  ref/                      # upstream V2 + FlashCallback
  build/

Entities

EntityIdentityRole
ERC20m_token_id: u8Multi-instance test token; ERC20.address(0) / address(1)
UniswapV2Pairm_token0, m_token1Reserves, mint / burn / swap, TWAP accumulators
UniswapV2Factory(singleton)deploy UniswapV2Pair(t0, t1), pair registry

Factory create (no hand-rolled CREATE2):

createPair(tokenA: address, tokenB: address)
    where tokenA != tokenB : throw 1
    /* … zero / duplicate guards … */
=> [
    deploy UniswapV2Pair(min_addr(tokenA, tokenB),
                          max_addr(tokenA, tokenB))
]

The deployed address is exactly UniswapV2Pair.address(min(tokenA, tokenB), max(tokenA, tokenB)).

Phased liquidity and swaps

mint / burn / sync / skim capture both token balances in a read: phase, then write reserves or payouts in a later phase. swap is three phases — optimistic transfer, optional flash callback, then K-invariant gate:

swap(amount0_out: U256, amount1_out: U256, to: address, data: CamData)
    where amount0_out > 0 || amount1_out > 0 : throw 200
    /* … reserve bounds … */
=> [
    optimistic: [
        transfer(to, amount0_out) ~> m_token0
        transfer(to, amount1_out) ~> m_token1
    ]
    callback: [
        ~> to with { value: 0, data: data }
        var bal0 = balanceOf(sys::address) ~> m_token0;
        var bal1 = balanceOf(sys::address) ~> m_token1;
    ]
    check where k_holds(bal0, bal1, m_reserve0, m_reserve1,
                        amount0_out, amount1_out) : throw 203 : []
]

Per-phase where on check: sees post-callback bal0 / bal1 (Execution Model).

ERC-20 extras

  • EIP-2612 permit — evm::ecrecover + evm::keccak256Packed over an EIP-712 digest; m_nonces bumped in the matching transform (examples/uniswap-v2/ERC20.cam).
  • Wrapping TWAP math — +% on cumulative prices (Checked vs Wrapping Arithmetic).
  • Vec push — factory m_all_pairs.push(pair) in a member transform.
  • import "./shared.cam" — shared pures without duplicating entities.

Build and test

cargo run -p cambrian-transpiler --release -- \
    --project examples/uniswap-v2/project.yaml

cd examples/uniswap-v2/build
bash setup.sh
forge build
forge test -vv

foundry.via_ir: true is set in project.yaml for the generated Foundry config. Lean: project.lean.yaml. Details and the closed gap log (G-U1…G-U10) are in examples/uniswap-v2/README.md and PLAN.md.