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 / .multi.invariant)
UniswapV2Pair.cam (+ .test / .fuzz / .invariant)
UniswapV2Factory.cam (+ .test / .invariant / .multi.invariant)
ref/ # upstream V2 + FlashCallback
build/ # generated EVM project
build-lean/ # generated Lean project
The *.multi.invariant.cam files hold multi-entity invariants and are
listed only in project.lean.yaml.
Entities
| Entity | Identity | Role |
|---|---|---|
ERC20 | m_token_id: u8 | Multi-instance test token; ERC20.address(0) / address(1) |
UniswapV2Pair | m_token0, m_token1 | Reserves, 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_addr(tokenA, tokenB), max_addr(tokenA, tokenB))
(min_addr / max_addr are pure fns in UniswapV2Factory.cam).
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::keccak256Packedover an EIP-712 digest;m_noncesbumped in the matching transform (examples/uniswap-v2/ERC20.cam). - Wrapping TWAP math —
+%on cumulative prices (Checked vs Wrapping Arithmetic). Vecpush — factorym_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. As with Governor, the only transpiler warning about the
YAML is an F7 note that fuzz.shrink has no effect. Lean:
cambrian-transpiler --project examples/uniswap-v2/project.lean.yaml
cd examples/uniswap-v2/build-lean && lake build
Details and the closed gap log (G-U1…G-U9) are in
examples/uniswap-v2/README.md and PLAN.md.