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
| 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(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::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. Lean: project.lean.yaml. Details and the closed gap
log (G-U1…G-U10) are in examples/uniswap-v2/README.md and
PLAN.md.