|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +CosmWasm DEX aggregator smart contract for the Injective blockchain. Routes swaps through multiple AMM pools, orderbook contracts, and CLMM (Concentrated Liquidity) pools in parallel, multi-hop paths with automatic CW20/native token conversion. Also supports **FlashRoute** — capital-free CLMM flash-arb (borrow from a CLMM pool's `Flash {}`, run a cycle through other venues, repay principal+fee, keep the surplus; see `docs/flash_route_plan.md`). Cargo workspace with three members: `dex_aggregator` (main contract), `mock_swap` (test helper), and `mock_clmm_flash` (test flash-pool helper). Deployed on Injective mainnet (Code ID 1892, address `inj1a4qvqym6ajewepa7v8y2rtxuz9f92kyq2zsg26`). |
| 6 | + |
| 7 | +## Build, Test, and Deploy Commands |
| 8 | + |
| 9 | +```bash |
| 10 | +# Development build |
| 11 | +cargo build |
| 12 | + |
| 13 | +# Production WASM build (uses cosmwasm/workspace-optimizer:0.17.0 Docker image) |
| 14 | +# Outputs dex_aggregator.wasm, mock_swap.wasm, mock_clmm_flash.wasm to ./artifacts/ |
| 15 | +# (runs --locked; if you added a member/dep, refresh Cargo.lock with a local |
| 16 | +# `cargo build` first, or the optimizer aborts on a stale lock) |
| 17 | +./build_release.sh |
| 18 | + |
| 19 | +# Run tests (MUST run ./build_release.sh first — see note below) |
| 20 | +cargo test |
| 21 | + |
| 22 | +# Run a single test |
| 23 | +cargo test <test_name> -- --nocapture |
| 24 | + |
| 25 | +# Lint |
| 26 | +cargo clippy --all-targets |
| 27 | + |
| 28 | +# Generate JSON schemas to contracts/dex_aggregator/schema/ |
| 29 | +cd contracts/dex_aggregator && cargo run --example schema |
| 30 | +``` |
| 31 | + |
| 32 | +**CRITICAL: Run `./build_release.sh` before `cargo test`.** Integration tests use `include_bytes!` to embed WASM artifacts at compile time. Tests will fail to compile or test stale code if artifacts aren't rebuilt after source changes. |
| 33 | + |
| 34 | +### Deployment (uses `injectived` CLI) |
| 35 | + |
| 36 | +```bash |
| 37 | +./scripts/upload_code_mainnet.sh # Upload new code to mainnet |
| 38 | +./scripts/deploy_mainnet.sh # Instantiate on mainnet (edit CODE_ID first) |
| 39 | +./scripts/deploy_testnet.sh # Deploy on testnet (chain ID: injective-888) |
| 40 | +``` |
| 41 | + |
| 42 | +## Architecture |
| 43 | + |
| 44 | +### Key Files (contracts/dex_aggregator/src/) |
| 45 | + |
| 46 | +| File | Purpose | |
| 47 | +|------|---------| |
| 48 | +| `contract.rs` | Entry points: `instantiate`, `execute`, `query`, `reply`. Routes `ExecuteMsg` variants to handlers (incl. `FlashRoute` / `FlashCallback`). | |
| 49 | +| `msg.rs` | All message types. Submodules: `amm`, `orderbook`, `clmm`, `cw20_adapter`, `reflection`. Defines `Stage > Split > Operation` route structure. `clmm::ClmmPoolFlashMsg` + `ExecuteMsg::FlashRoute`/`FlashCallback` for flash-arb. | |
| 50 | +| `execute.rs` | Core swap logic (`execute_aggregate_swaps_internal`, `create_swap_cosmos_msg`). Flash entry points: `execute_flash_route` (fires the pool's `Flash`), `execute_flash_callback` (borrower callback → runs the cycle via `proceed_to_next_step`). Admin functions: `set_fee`, `remove_fee`, `update_fee_collector`, `update_admin`, `emergency_withdraw`, `register_tax_token`, `deregister_tax_token`. | |
| 51 | +| `reply.rs` | Submessage reply state machine. Manages `Awaiting` states. Core function `proceed_to_next_step` drives stage-by-stage execution. Fee deduction via `apply_fee` at path completion. `finalize_route` disposes the final output: pay the user, or (flash) repay `principal+fee` to the pool and forward the surplus. | |
| 52 | +| `state.rs` | Storage: `CONFIG`, `FEE_MAP`, `ACTIVE_ROUTES`, `SUBMSG_REPLY_STATES`, `REPLY_ID_COUNTER`, `TAX_TOKEN_REGISTRY`, `PENDING_FLASH`. Defines `ExecutionState`, `SubmsgReplyState`, `Awaiting`, `RoutePlan` (with `flash_repayment`), `FlashRepayment`, `PendingFlashCtx`. | |
| 53 | +| `query.rs` | `simulate_route`, `query_config`, `query_fee_for_pool`, `query_all_fees`. Contains unit tests. | |
| 54 | +| `error.rs` | `ContractError` enum with `thiserror`. | |
| 55 | + |
| 56 | +### Execution Flow |
| 57 | + |
| 58 | +1. User calls `ExecuteRoute` (native funds) or sends CW20 via `Receive` hook |
| 59 | +2. `execute_aggregate_swaps_internal` validates input, creates `ExecutionState`, calls `proceed_to_next_step` |
| 60 | +3. Each stage: calculates per-split amounts, dispatches CW20/native conversions if needed (`Awaiting::Conversions`) |
| 61 | +4. Executes parallel swap submessages, each tracked by unique reply IDs in `SUBMSG_REPLY_STATES` |
| 62 | +5. `handle_swap_reply` processes each reply; for multi-hop paths, chains to next operation |
| 63 | +6. Mid-path conversions handled via `Awaiting::PathConversion` |
| 64 | +7. After final stage: normalizes output assets (`Awaiting::FinalConversions`), checks `minimum_receive`, sends to user |
| 65 | + |
| 66 | +### Supporting Contracts |
| 67 | + |
| 68 | +- `mock_swap` (`contracts/mock_swap/src/lib.rs`) — Mock DEX with configurable rates, supports AMM/Orderbook/CLMM protocol types, used in integration tests |
| 69 | +- `mock_clmm_flash` (`contracts/mock_clmm_flash/src/lib.rs`) — Mock CLMM flash-loan pool: faithfully mirrors `choice_clmm_pool`'s flash interface (lend → `FlashCallback` → balance-delta repayment check + reentrancy lock + `GetConfig`) at the JSON wire level. The flash source in the `FlashRoute` integration tests; the aggregator is the borrower, so no separate borrower mock is needed. (The real pool can't be embedded — `choice_exchange` is cosmwasm-std 2.x vs this workspace's 3.x.) |
| 70 | +- `cw20_adapter` and `cw20_base` — Pre-compiled WASMs in project root, not built from this workspace |
| 71 | + |
| 72 | +## Code Conventions |
| 73 | + |
| 74 | +### Naming |
| 75 | +- `snake_case` for functions, variables, module names |
| 76 | +- `PascalCase` for types, enums, structs, enum variants |
| 77 | +- `UPPER_SNAKE_CASE` for constants |
| 78 | + |
| 79 | +### Patterns |
| 80 | +- Entry points use Injective custom types: `DepsMut<InjectiveQueryWrapper>`, `Response<InjectiveMsgWrapper>` |
| 81 | +- Messages use `#[cw_serde]` macro; query enum uses `#[derive(QueryResponses)]` with `#[returns(...)]` |
| 82 | +- Error handling: `ContractError` enum via `thiserror`, propagated with `?` |
| 83 | +- State: `cw-storage-plus` types — `Item<T>` for singletons, `Map<K, V>` for key-value stores |
| 84 | +- Execute handlers return `Result<Response<InjectiveMsgWrapper>, ContractError>` |
| 85 | +- Query handlers return `StdResult<Binary>` |
| 86 | +- Admin checks: `info.sender != config.admin` → `ContractError::Unauthorized {}` |
| 87 | +- Response attributes for tracking: `.add_attribute("action", "...")` |
| 88 | + |
| 89 | +### Asset Handling |
| 90 | +- `amm::AssetInfo` enum: `Token { contract_addr }` (CW20) or `NativeToken { denom }` (bank) |
| 91 | +- Tax tokens in `TAX_TOKEN_REGISTRY` use `reflection::ExecuteMsg::TaxExemptTransfer` / `TaxExemptSend` |
| 92 | +- CW20 tokens sent to pools via `Cw20ExecuteMsg::Send`; native tokens as `funds` in `WasmMsg::Execute` |
| 93 | + |
| 94 | +### Submessage Reply Pattern |
| 95 | +- Each swap gets a unique `submsg_id` from `REPLY_ID_COUNTER` (monotonically incrementing) |
| 96 | +- `SubmsgReplyState` maps `submsg_id` → `master_reply_id`, `split_index`, `op_index` |
| 97 | +- `ExecutionState` stored in `ACTIVE_ROUTES` keyed by `master_reply_id` |
| 98 | +- All submessages use `SubMsg::reply_on_success` |
| 99 | +- Reply amounts parsed from wasm event attributes: `return_amount` (AMM), `swap_final_amount` (orderbook), `amount_out` (CLMM), `post_tax_amount` (tax tokens) |
| 100 | + |
| 101 | +## Testing |
| 102 | + |
| 103 | +- **Integration tests** (`tests/integration.rs`): Uses `injective-test-tube` for local chain simulation. `setup()` deploys all contracts, returns `TestEnv` with admin/user accounts and contract addresses. |
| 104 | +- **Unit tests** (`contracts/dex_aggregator/src/query.rs`): Simulation and fee query tests using `mock_dependencies()`. |
| 105 | +- Mock swap contracts configured with `SwapConfig { rate, protocol_type, input_decimals, output_decimals, ... }`. |
| 106 | +- WASM artifacts loaded via `include_bytes!` — stale artifacts mean stale tests. |
| 107 | + |
| 108 | +## Important Notes |
| 109 | + |
| 110 | +- `reply.rs` is the most complex module — state machine changes require careful review of all `Awaiting` state transitions |
| 111 | +- Orderbook swaps only support native token inputs/outputs; amounts rounded to `min_quantity_tick_size` |
| 112 | +- CLMM swaps support both native and CW20 tokens; no rounding needed. Pre-execution `Quote` query computes `minimum_amount_out` with 0.5% slippage |
| 113 | +- `FPDecimal` (from `injective-math`) for orderbook quantities; `Uint128`/`Decimal` (from `cosmwasm-std`) for everything else (including CLMM) |
| 114 | +- Fees deducted at path completion (end of a split's operation chain), not per-operation |
| 115 | +- **FlashRoute** (`docs/flash_route_plan.md`): a flash-arb cycle must repay in the *borrowed* asset, so it ends in `flash_asset` (gated by `min_profit`), not an A→B user swap. The whole cycle runs depth-first inside the pool's `FlashCallback`, so repayment settles before the pool's repay check — the pool reverts the tx if unrepaid. Repay uses the same Bank `Send` / CW20 `Transfer` (never CW20 `Send`) the pool requires. `FlashCallback` is gated on `PENDING_FLASH` + `info.sender == flash_pool`; the cycle may not route through `flash_pool` (reentrancy lock). |
| 116 | +- CI (`.github/workflows/test.yml`) runs `cargo build --verbose && cargo test --verbose` on push/PR to main |
0 commit comments