|
1 | 1 | # AGENTS.md |
2 | 2 |
|
3 | | -## Branching |
| 3 | +Solidity interfaces, libraries, and reference mocks for Base's precompiles: **B20** (ERC-20 superset |
| 4 | +with roles, policies, pausing, permits, memos), **PolicyRegistry** (allowlist/blocklist singleton), |
| 5 | +and **ActivationRegistry** (feature flags). The Solidity mocks must mirror the Rust implementations |
| 6 | +in base/base **slot-for-slot** — storage layout parity is the core invariant of this repo. |
| 7 | + |
| 8 | +## Commands |
| 9 | + |
| 10 | +```bash |
| 11 | +forge build # compile (solc 0.8.30) |
| 12 | +forge test -v # unit tests against mocked precompiles (fast; run first) |
| 13 | +forge test -v --match-test <name> # one test; --match-contract <name> for one suite |
| 14 | +forge fmt # format Solidity (CI gates on `forge fmt --check`) |
| 15 | +python3 script/check-coverage.py # every interface function has a test (CI gate) |
| 16 | +make coverage # lcov + HTML report (needs genhtml) |
| 17 | + |
| 18 | +base-forge test # same suite vs the real Rust precompiles, in-process (no node); auto-detects |
| 19 | +make smoke-setup # one-time venv setup — requires Python 3.13 exactly |
| 20 | +make smoke-all # live-RPC smoke journeys (needs .env; see below) |
| 21 | +make fork-tests # same suite vs a real base-anvil node (CI harness; patched anvil/forge) |
| 22 | +``` |
| 23 | + |
| 24 | +Test tiers, in the order to reach for them: **unit** (mocks, no network) → **live precompile** |
| 25 | +(real Rust precompiles — in-process via `base-forge test`, or vs a node via `make fork-tests`; |
| 26 | +cross-validates layout/behavior) → **smoke** (real txs against a live chain). |
| 27 | + |
| 28 | +## Testing |
| 29 | + |
| 30 | +- Unit tests live in `test/unit/{Feature}/{Name}.t.sol`; names follow |
| 31 | + `test_{function}_{outcome}_{variant}`, e.g. `test_allowance_success_zeroByDefault`. |
| 32 | +- Tests assert precompile storage directly via `vm.load` against the slot constants in |
| 33 | + `test/lib/mocks/MockB20Storage.sol`. **A live-precompile failure with passing unit tests means the |
| 34 | + Solidity reference and the Rust impl have diverged** — see `LIVE_PRECOMPILE_TESTING.md`, don't just |
| 35 | + patch the test. |
| 36 | +- Live-precompile tests need the patched binaries from |
| 37 | + [base/base-anvil](https://github.com/base/base-anvil); **stock forge/anvil will not work**. Install |
| 38 | + them alongside stock Foundry with `base-foundryup` (it never touches your `forge`/`anvil`). Common |
| 39 | + path — in-process, no node: `base-forge test`; `BaseTest` auto-detects and `setUp` logs **LIVE |
| 40 | + PRECOMPILE mode** vs **REFERENCE mode**. CI/node path: `make fork-tests` boots `anvil --base` and |
| 41 | + activates the gated features for you (override the binaries with `ANVIL_BIN` / `FORGE_BIN`). Pass |
| 42 | + forge args via `make fork-tests ARGS="-vvvv --match-test <name>"`. Details: |
| 43 | + `LIVE_PRECOMPILE_TESTING.md`, `script/fork/README.md`. |
| 44 | +- Smoke tests need `.env` (copy `.env.template`): `RPC_URL`, `DEPLOYER_PK`, `USER2_PK` — |
| 45 | + **testnet keys only**, both accounts funded. Journeys **skip (not fail)** when the target chain |
| 46 | + hasn't activated the feature — a skip is not a pass. Run one journey with |
| 47 | + `make smoke-{factory,asset,stablecoin,policy,invariants}`; audit mode: |
| 48 | + `make smoke-all KEEP_GOING=1`. |
| 49 | +- Fuzz runs: 256 default, 10 under `FOUNDRY_PROFILE=fork` (RPC round-trips are slow). |
| 50 | + |
| 51 | +## Project structure |
| 52 | + |
| 53 | +``` |
| 54 | +src/StdPrecompiles.sol # canonical precompile addresses + typed handles |
| 55 | +src/interfaces/ # IB20, IB20Asset, IB20Stablecoin, IB20Factory, IPolicyRegistry, IActivationRegistry |
| 56 | +src/lib/ # B20Constants (role/policy ids), B20FactoryLib (createB20 encoders) |
| 57 | +src/impls/ # reserved for reference impls (currently empty; mocks fill that role) |
| 58 | +test/unit/ # one directory per feature; slot-level assertions |
| 59 | +test/regression/ # interface renames/removals guard (B20Renames.t.sol, B20Removals.t.sol) |
| 60 | +test/lib/ # BaseTest.sol, B20Test.sol, mocks/ (reference behavior + storage layout) |
| 61 | +script/smoke/ # Python 3.13 live-node smoketest (web3.py); see script/smoke/README.md |
| 62 | +script/fork/ # node-based live-precompile runner (anvil + patched forge); see script/fork/README.md |
| 63 | +docs/ # specs: docs/B20/README.md, docs/PolicyRegistry/, docs/ActivationRegistry/ |
| 64 | +``` |
| 65 | + |
| 66 | +Deeper reading: `LIVE_PRECOMPILE_TESTING.md` (cross-validation architecture), `docs/B20/README.md` (B20 spec). |
| 67 | + |
| 68 | +## Code style |
| 69 | + |
| 70 | +- Solidity formatting comes from `foundry.toml`: 120-char lines, 4-space tabs, double quotes, |
| 71 | + long int types (`uint256`, never `uint`). Run `forge fmt` before committing. |
| 72 | +- Interfaces use pragma `>=0.8.20 <0.9.0` (consumer compatibility); test/mock code uses `^0.8.20`, |
| 73 | + compiled with the pinned solc 0.8.30 from `foundry.toml`. |
| 74 | +- Errors are custom types with parameters, e.g. `error InvalidSupplyCap(uint256 currentSupply, |
| 75 | + uint256 proposedCap)` — never `require` strings. |
| 76 | +- Import via the public remappings `base-std/=src/` and `base-std-test/=test/`. |
| 77 | +- Mock state uses ERC-7201 namespaced storage; new state must follow the same pattern. |
| 78 | +- Python (`script/`): 3.13, PEP 8, snake_case functions, frozen dataclasses for config. |
| 79 | + `web3>=7.6,<8` is the only dependency; both runners share `script/smoke/.venv`. |
| 80 | + |
| 81 | +## Git workflow |
4 | 82 |
|
5 | 83 | - **`main` is the default branch.** Branch from `main` and open PRs against `main`. |
6 | | -- Ignore `master` — it is stale. Do not branch from it, target it in PRs, or use it as a base reference. |
| 84 | +- Conventional Commits with optional scope: `feat(b20): ...`, `fix(smoke): ...`, `test:`, `docs:`, |
| 85 | + `chore:`. Put the rationale in the body, not just the what. |
| 86 | +- CI on every PR: `forge build`, `forge test`, `forge fmt --check`, |
| 87 | + `python3 script/check-coverage.py`, coverage comment. Live-precompile tests run in a separate workflow. |
| 88 | + |
| 89 | +## Boundaries |
| 90 | + |
| 91 | +- **Don't change the precompile addresses** in `src/StdPrecompiles.sol` or the feature IDs in |
| 92 | + `script/smoke/config.py` / `test/lib/mocks/ActivationRegistryFeatureList.sol` — they are |
| 93 | + canonical constants shared with base/base; coordinate changes there first. |
| 94 | +- **Don't reshape mock storage layout unilaterally** — match `MockB20Storage.sol` slot constants |
| 95 | + and cross-validate with `make fork-tests` before merging. |
| 96 | +- **Don't commit `.env` or real private keys** — `.env` is gitignored; use funded testnet keys. |
| 97 | +- **Don't hand-edit ABIs for the smoke tests** — `script/smoke/abis.py` loads them from `out/`; |
| 98 | + run `forge build` instead. |
| 99 | +- **Don't weaken a slot-assertion test to make live-precompile tests pass** — investigate the divergence via |
| 100 | + `LIVE_PRECOMPILE_TESTING.md` and fix the side that's wrong. |
0 commit comments