Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with
code in this repository.

## What this is

Solidity contracts for "flows" — token movement primitives (ERC20/721/1155)
driven by rainlang expressions. A registered evaluable produces a stack; the
contract parses that stack into transfers and executes them atomically. V5
(`src/interface/IFlowV5.sol`) is the current live interface; everything under
`src/interface/deprecated/` (V1-V4 plus the V5 ERC-mint/burn variants) is
**frozen ABI** for historical deployments — do not change function signatures,
event signatures, or struct layouts there.

## Commands

All commands run inside the rainix nix dev shell (`nix develop -c <task>`).

- Build: `nix develop -c forge build`
- Test (full suite): `nix develop -c rainix-sol-test`
- Single test: `nix develop -c forge test --match-test <name>` (or
`--match-contract`)
- Static (slither + `forge fmt --check`): `nix develop -c rainix-sol-static`
- Legal (REUSE): `nix develop -c rainix-sol-legal`
- Format: `nix develop -c forge fmt`

After bumping `flake.lock` (rainix → foundry.nix), the foundry nightly often
shifts and `rainix-sol-static` will fail on `forge fmt --check`. Run
`nix develop -c forge fmt` and commit the sweep before pushing.

`gh` is in the dev shell, not the host. Run `nix develop -c gh ...`.

## Architecture

- `src/concrete/Flow.sol` — the only live implementation. Clone-and-init pattern
via `ICloneableV2`. Implementation contract calls `_disableInitializers()` in
its constructor; clones are deployed and initialized via a factory. `flow`
carries `nonReentrant`. The `flowInit` loop trusts the deployer (per the
explanatory comments above the slither-disable annotations).
- `src/lib/LibFlow.sol` — `internal` library. `stackToFlow` parses the
evaluator's stack into a `FlowTransferV1` by consuming sentinel-delimited
tuples (size 4 for ERC20, 4 for ERC721, 5 for ERC1155); the per-token `flow*`
helpers enforce `from ∈ {msg.sender, address(this)}` then call
`safeTransfer{,From}`. Reentrancy protection is the caller's responsibility —
`Flow.flow` provides it.
- `src/interface/IFlowV5.sol` — live interface. Re-exports `RAIN_FLOW_SENTINEL`,
`MIN_FLOW_SENTINELS = 3`, `FlowTransferV1`, `ERC20Transfer`, `ERC721Transfer`,
`ERC1155Transfer` from `deprecated/v4/IFlowV4.sol`. The constant chain is
`V3 → V4 → V5`; V4 is the canonical declaration, V5 only re-exports.
- `src/error/ErrFlow.sol` — custom errors. Live: `UnregisteredFlow(bytes32)`,
`UnsupportedERC{20,721,1155}Flow`, `UnsupportedFlowInputs`,
`InsufficientFlowOutputs`. Six other errors and an empty `ErrFLow {}` (sic)
contract are unreferenced anywhere in `src/`, `test/`, or `lib/` — the empty
contract has no NatSpec or source-level rationale.

### Stack layout

Stack is consumed top → bottom: ERC20 transfers first, then ERC721, then
ERC1155, each section terminated by `RAIN_FLOW_SENTINEL`. **Three sentinels are
required** even when sections are empty — `LibFlow.stackToFlow` makes three
`consumeSentinelTuples` calls. The rainlang example NatSpec in
`IFlowV4.sol`/`IFlowV5.sol` shows only one sentinel; do not use that example as
a template.

### Why no native ETH

`Flow` inherits `Multicall` (delegatecall in a loop), which double-spends
`msg.value`. Native flows were removed in V2; do not reintroduce them without
replacing the batching primitive (samczsun, "two rights might make a wrong").
The contract-level NatSpec on `Flow.sol` documents this.

## Tooling conventions

- `slither.config.json` excludes `assembly-usage`, `solc-version`,
`unused-imports`, `unindexed-event-address`. The `unindexed-event-address`
exclusion exists because every deprecated `Initialize(address sender, …)`
event would re-trigger it, and adding `indexed` would break ABI compatibility
for historical indexers. Do not remove without a versioning plan.
- Inline `slither-disable-next-line <rule>` annotations require an explanatory
comment above them (project rule, follows global preference).
- `.pre-commit-config.yaml` is generated by `git-hooks-nix` (a transitive
dependency of rainix) on every dev shell entry. The file embeds machine-local
`/nix/store` paths and is `.gitignore`d. Do not commit it.
- `lib/` is git submodules managed via `foundry.lock`. Do not modify submodules
directly. To bump a dep: update its commit hash in `foundry.lock`, then run
`nix develop -c forge update lib/<name>`.

## Branch / PR / commit conventions

- Branch names: `YYYY-MM-DD-<topic>` (e.g., `2026-04-25-nix`).
- Commit subjects: short lowercase imperatives (`bump nix`,
`consolidate flow contracts`).
- Merges: regular merge only — never squash (history preservation).
- After local changes, before pushing: `nix develop -c forge build`,
`rainix-sol-test`, `rainix-sol-static`, `rainix-sol-legal` should all exit 0.
Beware that piping these through `tail`/`head` masks non-zero exit codes —
capture exit codes explicitly when validating.

## Frozen ABI policy

Files under `src/interface/deprecated/` retain their original signatures,
events, and struct field layouts. Acceptable changes: NatSpec corrections,
comment fixes, file-internal lint annotations. Unacceptable changes: anything
observable in the ABI (function signatures, event topics, struct member
order/types). When an audit flags a real defect on a deprecated interface, the
disposition is "deprecate / migrate consumers to V5", not a signature change.

**Audit scope: exclude `src/interface/deprecated/` from `/audit` and any other
systematic codebase review.** These files are frozen — agents should not
enumerate them, file findings against them, or propose tests for them. The
recently-completed audit (issues filed against this repo with the `audit` label)
closed every deprecated-interface finding under this same policy; re-discovering
them in a future audit just produces noise. In-scope audit targets:
`src/concrete/`, `src/error/`, `src/lib/`, `src/interface/IFlowV5.sol` (live
current interface only), and `test/`.

## Foundry config (`foundry.toml`)

`solc = "0.8.25"`, `optimizer = true`, `optimizer_runs = 1000000`,
`bytecode_hash = "none"`, `cbor_metadata = false`, fuzz `runs = 2048` with
`seed = "0xdeadbeef"`. Per-test `forge-config: default.fuzz.runs = 100`
overrides exist on slow fuzz tests; do not remove without benchmarking.
27 changes: 14 additions & 13 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ version = 1

[[annotations]]
path = [
".devcontainer.json",
".gitignore",
".gitmodules",
".gas-snapshot",
".github/**/",
".vscode/**/",
"README.md",
"flake.lock",
"flake.nix",
"foundry.lock",
"foundry.toml",
"slither.config.json",
".devcontainer.json",
".gitignore",
".gitmodules",
".gas-snapshot",
".github/**/",
".vscode/**/",
"CLAUDE.md",
"README.md",
"flake.lock",
"flake.nix",
"foundry.lock",
"foundry.toml",
"slither.config.json",
]
SPDX-FileCopyrightText = "Copyright (c) 2020 Rain Open Source Software Ltd"
SPDX-License-Identifier = "LicenseRef-DCL-1.0"
SPDX-License-Identifier = "LicenseRef-DCL-1.0"
Loading