Crane

Diamond-first Solidity framework for modular, upgradeable contracts using the ERC2535 Diamond pattern.

Scope

Crane provides:

  • Standardized Facet-Target-Repo architecture.
  • Deterministic deployment infrastructure (CREATE3 and Diamond packages).
  • Reusable access control, introspection, and token implementations.
  • Protocol integration services for common DeFi primitives.
  • Consistent testing patterns.

Core Benefit: Facet Reuse

Facets are separate contracts. You must deploy a facet instance on each chain where you use it. CREATE3 can place those instances at the same address on every chain, but they are still distinct deployments (one per chain).

Within one chain, a single facet instance is shared by many Diamond proxies (deploy logic once, attach everywhere).

flowchart TB
    subgraph Eth["Ethereum"]
        F1["ERC20Facet<br/>instance on Ethereum"]:::facet
        P1["Proxy A"]
        P2["Proxy B"]
        F1 --> P1
        F1 --> P2
    end

    subgraph Arb["Arbitrum"]
        F2["ERC20Facet<br/>instance on Arbitrum"]:::facet
        P3["Proxy C"]
        F2 --> P3
    end

    subgraph Base["Base"]
        F3["ERC20Facet<br/>instance on Base"]:::facet
        P4["Proxy D"]
        F3 --> P4
    end

    %% Dark-theme safe: deep slate fill + light text (never bright green + white)
    classDef facet fill:#2c3e50,stroke:#80cbc4,color:#ecf0f1,stroke-width:2px

On a given chain, deployment cost for facet bytecode is paid once; later proxies mainly pay for storage init and minimal proxy deploy. Cross-chain, you pay to deploy the facet (and factories/packages) on each chain you support—CREATE3 keeps addresses predictable, not shared across chains.

Packages define the set of facets and the initialization steps required to produce a functioning proxy. The same package produces consistent instances at predictable addresses when given the same arguments.

How to Use These Docs

Navigation is driven by SUMMARY.md (GitBook TOC — each page listed once).

Prerequisites

Experienced Solidity developers. Familiarity with:

  • ERC2535 Diamonds.
  • Proxy patterns and storage collisions.
  • Foundry.

License

AGPL-3.0-or-later.

Getting Started

Crane is a Diamond-first (ERC-2535) framework for building modular, upgradeable Solidity contracts with deterministic deployment, reusable logic, and first-class support for AI agents.

Why reuse matters

Security. Prefer attaching already deployed and verified facets (via DFPkgs) instead of redeploying large surfaces of new bytecode—especially when an AI agent writes the change.

Cost. Facets and packages deployed once can be reused by many proxies and projects, so you do not pay full deployment cost for the same logic every time.

Cross-chain reproducibility. CREATE3 salts yield the same addresses when configuration matches.

Documentation map

TopicPage
CREATE3 / new chain / factory reuseCREATE3 & New Chain Setup
DFPkg operationsDiamond Factory Packages · DFPkg Pattern
RegistriesRegistries
Testing / TestBasesTesting Patterns
DEX / lending portsDEX Integrations · Lending
Sets / ConstProdUtilsUtilities Overview · Sets · Math
Building modulesBuilding with Crane
Architecture mapCodebase Map
AI agent skillsAgent Skills

Install

git clone --recurse-submodules https://github.com/cyotee/crane.git
cd crane
forge build

# Core path (matches CI)
FOUNDRY_PROFILE=ci forge build
FOUNDRY_PROFILE=ci forge test

As a Foundry dependency:

forge install cyotee/crane

Update your remappings.txt and foundry.toml (see this repo for aliases such as @crane/).

Bootstrap factories in tests:

import {InitDevService} from "@crane/contracts/InitDevService.sol";

(ICreate3FactoryProxy create3Factory, IDiamondPackageCallBackFactory diamondFactory) =
    InitDevService.initEnv(address(this));

For AI agents

  1. Load skills — Prefer in-repo .claude/skills/ and/or install marketplaces:
  2. Start with crane-architecture, crane-deployment, and crane-testing.
  3. Use Facet-Target-Repo + DFPkg for new features — Repo for storage, Target for logic, Facet for Diamond exposure.
  4. Bootstrap via Init*Service or Crane test bases — avoid ad-hoc new for production deployment paths.
  5. Follow NatSpec conventions in AGENTS.md and NatSpec docs.
  6. Optional BattleChain gate — significant factories/packages may be exercised on BattleChain before mainnet promotion; see BattleChain.

Next steps

Building Custom Modules with Crane

This guide is written for AI agents and developers who want to extend Crane or build production DeFi features on top of it.

Crane's value proposition: write once (facet + package), deploy cheaply everywhere (deterministic proxies), test rigorously, and let other agents reuse your work via skills.

The Mandatory Pattern: Facet-Target-Repo (FTR)

Every substantial feature follows three files + optional supporting contracts:

  1. *Repo.sol (library) — All storage + internal accessors + guard functions (_onlyXxx).

    • Use dual _layoutStruct() overloads.
    • Storage slot: hierarchical keccak, e.g. "crane.feature.mything".
    • Never use state variables in the library.
  2. *Target.sol (contract) — Business logic that inherits interfaces and calls into the Repo.

    • Thin; delegates heavy lifting to Repo.
  3. *Facet.sol (contract) — Extends Target and implements IFacet for metadata (name, interfaces, funcs).

    • The only thing attached to Diamonds.

Supporting:

  • *Modifiers.sol — Thin modifiers delegating to Repo guards (e.g. onlyOperator).
  • *Service.sol — Stateless complex logic (use structs to avoid stack-too-deep).
  • *AwareRepo.sol — For injecting external addresses (e.g. router, vault).
  • I*DFPkg.sol + *DFPkg.sol — Bundle facets + init for repeatable Diamonds.

See crane-architecture skill and contracts/access/operable/ for the complete reference implementation.

Step-by-Step: New Feature

  1. Define the interface(s) first (contracts/interfaces/...).

    • Add full NatSpec + @custom:selector / signature / topiczero.
    • Wrap with // tag::IFoo[] ... // end::IFoo[].
  2. Create the Repo.

    • Dual functions for every operation.
    • _onlyXxx(Storage storage, ...) guards.
    • NatSpec + include tags.
  3. Implement Target + tests.

    • Use Behavior libraries for interface compliance.
    • Write handler + invariant tests for stateful properties.
  4. Create the Facet.

    • Implement facetName, facetInterfaces, facetFuncs, facetMetadata.
    • Add NatSpec.
  5. (Recommended) Create a DFPkg.

    • PkgInit and PkgArgs live in the interface.
    • Provide a deploy(...) helper.
    • Wire initAccount to apply cuts + call initializers.
  6. Write a FactoryService (if multiple related deploys).

    • Group facet + package deployment.
    • Always vm.label.
    • Salt = abi.encode(type(X).name)._hash().
  7. Add or update a skill under .claude/skills/.

    • Document usage, examples, gotchas.
    • Make it usable by other agents.
  8. Update docs (SUMMARY, relevant md files) and NatSpec extraction points.

Deployment Cost Savings

  • One facet deployment (gas paid once).
  • Each proxy instance costs only the minimal proxy + init calldata.
  • Same package + args = same address on every chain (CREATE3).
  • Agents reuse published facets/packages across projects.

NatSpec & Documentation Requirement

Every external/public symbol that forms part of the API must have:

  • /// @notice (and @dev/@param/@return as applicable).
  • @custom:signature and the matching selector (use cast sig).
  • // tag::SymbolName[] / // end::SymbolName[] exactly.

Interfaces get @custom:interfaceid.

Run cast to verify before committing.

See development/natspec.md and crane-natspec skill.

Testing

  • Co-locate TestBase_*.sol and Behavior_*.sol next to the code.
  • Use declarative invariants + handlers for fuzz.
  • Protocol ports have deep TestBase inheritance chains.

See crane-testing skill.

Security Model

  • BattleChain gate for factories, core DFPkgs, and major components.
  • Reentrancy protection (transient), operable access, multi-step ownership.
  • Comprehensive behavior + invariant testing.
  • Never bypass patterns for "simplicity".

Agent-Specific Tips

  • When asked to implement something, first consult the relevant Crane skill(s).
  • Prefer extending existing *AwareRepo + *Service rather than reinventing.
  • After shipping, package the knowledge into a new or updated skill.
  • Use CraneTest base for all specs.
  • Emit clear NatSpec — future agents (and docs) will include:: it.

Examples in the Repo

  • contracts/tokens/ERC20/ — Full native ERC20/2612/4626 with DFPkg.
  • contracts/access/operable/ — Canonical access control.
  • contracts/factories/... — All the deployment machinery itself.
  • Protocol integrations under contracts/protocols/ (study how they wrap external logic).

Start small, follow the pattern strictly, document everything, and publish the skill.

Other agents will thank you by reusing your facets and saving gas.

See also

Facet-Target-Repo

Every feature in Crane follows a three-layer architecture.

Layers

LayerFile SuffixResponsibilityDeployed?
Repo*Repo.solStorage layout and access functionsNo
Target*Target.solBusiness logic implementationNo
Facet*Facet.solDiamond exposure and metadataYes
flowchart TB
    subgraph Proxy["Diamond Proxy<br/>(deployed per instance)"]
        direction TB
        Storage["Storage<br/>(namespaced slots per Repo)"]
    end

    Facet["Facet<br/>(deployed once via CREATE3)"] --> Target["Target<br/>(business logic)"]
    Target --> Repo["Repo Library<br/>(storage access + guards)"]
    Repo -->|assembly slot binding| Storage

    classDef deployed fill:#2c3e50,stroke:#80cbc4,color:#ecf0f1
    classDef proxy fill:#34495e,stroke:#90a4ae,color:#ecf0f1
    class Facet deployed
    class Proxy proxy

The same Facet contract and address can be referenced by any number of proxies.

Repo

A Repo is a library that owns a storage struct and provides all read/write operations on it.

  • Storage slot is a bytes32 constant derived from a hierarchical name.
  • Dual _layoutStruct() functions: one that accepts an explicit slot, one that uses the constant.
  • Every state-mutating or view function on the layout has two overloads: one that receives Storage storage layoutStruct as the first parameter, and one that calls the first using _layoutStruct().

Example structure:

library OperableRepo {
    bytes32 internal constant STORAGE_SLOT = keccak256(abi.encode("crane.access.operable"));

    struct Storage {
        mapping(address => bool) isOperator;
        // ...
    }

    function _layoutStruct() internal pure returns (Storage storage layoutStruct) {
        return _layoutStruct(STORAGE_SLOT);
    }

    function _layoutStruct(bytes32 slot) internal pure returns (Storage storage layoutStruct) {
        assembly { layoutStruct.slot := slot }
    }

    function _isOperator(Storage storage layoutStruct, address account) internal view returns (bool) {
        return layoutStruct.isOperator[account];
    }

    function _isOperator(address account) internal view returns (bool) {
        return _isOperator(_layoutStruct(), account);
    }
}

Repos contain guard functions (_onlyOperator, _onlyOwner, etc.). These hold the actual access control logic.

Target

A Target is an abstract contract that inherits the required interfaces and implements the external functions by delegating to the corresponding Repo.

Targets contain the executable logic. They do not declare state variables.

Facet

A Facet inherits from the Target and implements IFacet.

contract OperableFacet is OperableTarget, IFacet {
    function facetName() external pure returns (string memory) {
        return type(OperableFacet).name;
    }

    function facetInterfaces() external view returns (bytes4[] memory) { ... }
    function facetFuncs() external view returns (bytes4[] memory) { ... }
    function facetMetadata() external view returns (string memory, bytes4[] memory, bytes4[] memory) { ... }
}

The facet is what gets cut into a Diamond. It exposes the function selectors that will be routed to the facet address.

Reuse

The separation produces reusable facets:

  • The facet contract contains only logic and a reference to immutable facet state (for packages). It has no proxy-specific storage.
  • All persistent state lives in the proxy under slots controlled by Repos.
  • The same facet bytecode and address can be attached to any number of proxies.
  • Because slot derivation is deterministic and namespaced, different features do not collide even when many facets are installed on one proxy.

Facets are deployed through Create3Factory.deployFacet. The resulting address is the same on every chain for a given creation code and salt. Packages reference these addresses immutably. Every proxy created from the package executes against the shared facet implementations.

This amortizes deployment gas for logic across all instances and all chains.

Modifiers

Access control modifiers live in thin *Modifiers.sol contracts:

abstract contract OperableModifiers {
    modifier onlyOperator() {
        OperableRepo._onlyOperator();
        _;
    }
}

Modifiers delegate to the guard functions in the Repo. The logic remains in one place.

Services

Complex orchestration that crosses multiple external contracts is placed in *Service.sol libraries. Services accept parameter structs to avoid stack-too-deep issues and remain stateless with respect to Diamond storage.

When to Introduce Each Layer

  • Add a Repo for any new persistent state.
  • Add a Target when the logic must be unit-testable independently of a Diamond.
  • Add a Facet when the functionality must be callable through a proxy.
  • Add a DFPkg when the feature must be composed into new Diamonds via the factory.
  • Add a Service when coordination with external routers, vaults, or other protocols is required.
  • Add an AwareRepo when a proxy must hold a reference to an external singleton (router, factory, vault).

Storage Slots

All persistent state in Crane lives in library-defined structs accessed via assembly slot binding. No contract or facet declares state variables. This design is core to the Facet-Target-Repo architecture: stateless, reusable facet implementations operate against per-proxy storage that is isolated by deterministic, namespaced slots.

Storage slots enable safe reuse of deployed facets and DFPkgs across many Diamond instances and chains. The same facet bytecode is attached to any number of proxies; each proxy's storage layout is governed exclusively by the Repos' slot constants.

ERC1967-Compliant Slot Derivation (LR-6)

All STORAGE_SLOT, DEFAULT_SLOT, or equivalent constants in Repos MUST use the ERC1967 derivation:

bytes32 internal constant DEFAULT_SLOT = bytes32(uint256(keccak256(abi.encode("your.hierarchical.slot.name"))) - 1);

Canonical example from contracts/registries/facet/FacetRegistryRepo.sol:

// tag::DEFAULT_SLOT[]
bytes32 internal constant DEFAULT_SLOT = bytes32(uint256(keccak256(abi.encode("crane.registries.facets"))) - 1);
// end::DEFAULT_SLOT[]

Other gold-standard compliant examples (post LR-6 alignment):

  • OperableRepo: STORAGE_SLOT = bytes32(uint256(keccak256(abi.encode("crane.access.operable"))) - 1)
  • ERC2535Repo: STORAGE_SLOT = bytes32(uint256(keccak256(abi.encode("eip.erc.2535"))) - 1)

Rationale (from PRD LR-6): Matches the established EIP-1967 standard (https://eips.ethereum.org/EIPS/eip-1967). The - 1 offset after the keccak provides a standard, collision-resistant, proxy-friendly slot. Using the direct keccak256(...) (without the cast and - 1) is non-compliant.

This applies to every Repo across the framework, including core access, introspection (ERC2535/ERC8109), registries, and protocol *AwareRepo libraries. The form ensures consistent layout computation across all EVM chains.

See also: PRD.md (LR-6 section) and the crane-architecture skill for slot rules.

Ties to NatSpec Standard and Include Tags (LR-1)

Storage symbols are documented to the same standard as all public surface:

  • Slot constants, Storage structs, and _layoutStruct overloads receive rich NatSpec (@dev, @param, @return).
  • They are wrapped with exact AsciiDoc include tags for extraction:
    // tag::STORAGE_SLOT[]
    bytes32 internal constant STORAGE_SLOT = bytes32(uint256(keccak256(abi.encode("eip.erc.2535"))) - 1);
    // end::STORAGE_SLOT[]
    
  • Dual _layoutStruct(bytes32) and _layoutStruct() overloads are similarly tagged (e.g. _layoutStruct(bytes32)[]).

This follows the canonical ERC8023 gold standard and AGENTS.md rules. Documentation tooling can include:: exact snippets. Custom NatSpec values for related selectors (e.g. IFacet methods facetInterfaces() = 0x2ea80826) are populated exclusively from docs/archive/reports/gap/CENTRALLY_COMPUTED_NATSPEC_VALUES.md via the dedicated verification script (scripts/foundry/ComputeNatSpecValues.s.sol). See NatSpec and Documentation.

Repos always document both the parameterized form (taking Storage storage layoutStruct) and the default overload.

Slot Naming Convention

Use hierarchical dot-notation for collision-free namespacing:

  • Core framework: crane.* (e.g. crane.access.operable, crane.registries.facets, crane.registries.packages)
  • EIP implementations: eip.erc.* (e.g. eip.erc.2535, eip.erc.8023)
  • Protocol integrations: protocols.dexes.{protocol}.{version}.* (e.g. protocols.dexes.balancer.v3.vault.aware)

The identical derivation rule is applied in every Repo.

Dual Layout Access

Every Repo provides two _layoutStruct functions:

// tag::_layoutStruct(bytes32)[]
function _layoutStruct(bytes32 slot_) internal pure returns (Storage storage layoutStruct_) {
    assembly { layoutStruct_.slot := slot_; }
}
// end::_layoutStruct(bytes32)[]

function _layoutStruct() internal pure returns (Storage storage layoutStruct) {
    return _layoutStruct(DEFAULT_SLOT);  // or STORAGE_SLOT
}
  • Parameterless: uses the Repo's constant (most call sites).
  • Parameterized: supports custom slots for testing, composition, or advanced multi-tenant scenarios.

Binding is 100% library-level (pure assembly). No state variables exist in Targets, Facets, or other contracts.

Function Overloading Convention

Every storage operation is provided in dual form:

function _canonicalFacet(Storage storage layoutStruct, bytes4 interfaceId) internal view returns (IFacet facet);
function _canonicalFacet(bytes4 interfaceId) internal view returns (IFacet facet) {
    return _canonicalFacet(_layoutStruct(), interfaceId);
}

Callers holding a Storage reference (e.g. inside other Repo functions or guards) pass it explicitly. External callers use the default overload. Guard functions (_onlyXxx) also follow this pattern.

Why Libraries + Storage Isolation

Benefits:

  • Layout defined once, used by all proxies that install the facet.
  • Explicit, auditable assembly assignment.
  • Zero risk of storage collisions between features or between independently authored code.
  • Stateless facets + deterministic slots = "deploy once, attach everywhere" reuse.
flowchart LR
    Repo["*Repo.sol<br/>_layoutStruct() + _layoutStruct(slot)<br/>(ERC1967 slots)"] -->|assembly| ProxyStorage["Diamond Proxy Storage<br/>(isolated per instance)"]
    ProxyStorage -->|namespaced| FeatureA["Feature A (e.g. operable)"]
    ProxyStorage -->|namespaced| Registries["Registries (facets/packages)"]
    ProxyStorage -->|namespaced| FeatureB["Protocol state (AwareRepos)"]

    classDef repo fill:#2c3e50,stroke:#80cbc4,color:#ecf0f1
    class Repo repo

The dual functions + ERC1967 slots guarantee isolation even when many facets (from multiple DFPkgs) are installed on one proxy.

Slot Examples (Canonical + Common)

  • crane.registries.facets (FacetRegistryRepo.DEFAULT_SLOT)
  • crane.registries.packages (DiamondFactoryPackageRegistryRepo.DEFAULT_SLOT)
  • crane.access.operable (OperableRepo.STORAGE_SLOT)
  • eip.erc.2535 (ERC2535Repo.STORAGE_SLOT — Diamond cut/loupe bookkeeping)
  • crane.access.erc8023 (MultiStepOwnableRepo)
  • protocols.dexes.balancer.v3.vault.aware (protocol AwareRepo pattern)

Per PRD LR-2, storage slots are foundational to these required content areas:

  • Chain setup via CREATE3 Package: Deploy your own Create3Factory using Create3FactoryDFPkg (and ICREATE3DFPkg). Resulting factories, registries, and all proxies use the ERC1967 slots defined here for consistent behavior on new chains. See deployment/create3.md (includes central NatSpec e.g. packageName 0xabc8b346, initAccount 0x870d4838) and deployment/dfpkg.md.
  • DiamondPackageCallBackFactory reuse: The package factory (interfaceId 0x949da331 from CENTRALLY_COMPUTED_NATSPEC_VALUES.md) does not need redeployment per chain. It reuses the same facets/packages whose storage access is governed by these slots. Explicit reuse guidance lives in the deployment docs above + CODEBASE_MAP.md.
  • Registries (Facet/Package/CallTarget): Purpose, auto-population (via Create3Factory + _register* calls during DFPkg init), and consumer interaction (canonical*, facetsOf*, queries) are all backed by Repos using ERC1967 slots. Full details: CODEBASE_MAP.md.
  • Ported protocols + test usage: All DEX/lending ports (CamelotV2, Uniswap, Aerodrome, BalancerV3, Aave, Euler) use *AwareRepos or service state with protocol-prefixed slots. Test via CraneTest + TestBase_* inheritance (e.g. TestBase_CamelotV2, TestBase_BalancerV3Vault) + handlers/Behaviors. See protocol docs under docs/protocols/, development/testing.md, and AGENTS.md testing section.
  • General utilities (Sets, ConstProdUtils, collections): AddressSet, Bytes4Set, etc. (and their *SetRepo helpers) are stored inside many Repo Storage structs (e.g. ERC2535Repo, FacetRegistryRepo). Math utilities like ConstProdUtils are used by protocol services alongside storage. Detailed coverage in CODEBASE_MAP.md under General Utilities.
  • Agent value proposition (LR-4): Reusing already-deployed verified facets/packages (via DFPkgs) eliminates agent-induced bugs in per-project redeploys and saves gas ("deploy once, attach everywhere", "agent-proof reuse"). Storage isolation via namespaced ERC1967 slots makes this safe and deterministic. See getting-started.md, building-with-crane.md, and AGENTS.md.

Related:

  • Architecture: concepts/facet-target-repo.md (Repos, dual layout, reuse), building-with-crane.md
  • NatSpec + verification: development/natspec.md (use ONLY central values)
  • Skills: .claude/skills/crane-architecture (Facet-Target-Repo + storage slots), crane-deployment
  • Full source reference: contracts/introspection/ERC2535/ERC2535Repo.sol, contracts/registries/facet/FacetRegistryRepo.sol, contracts/access/operable/OperableRepo.sol

Guidelines

  • Always derive new slots with the ERC1967 bytes32(uint256(keccak256(abi.encode(name))) - 1) form.
  • Give every feature its own namespace segment under the appropriate prefix.
  • Protocol state uses the protocols.* prefix (even if implemented in a *Service).
  • Never hardcode raw slot bytes outside the owning Repo's constant.
  • When adding NatSpec to a Repo, wrap the slot/Storage/_layout symbols with exact // tag::Name[] markers (no extra spaces) and use the central NatSpec values document for any referenced selectors.
  • Storage isolation and multi-instance safety must be validated in tests (LR-7).

Guard Functions and Modifiers

Access control logic resides in Repos. Modifiers are thin delegation wrappers.

Guard Functions

Repos implement _onlyXxx functions that perform the check and revert with the appropriate custom error.

function _onlyOperator(Storage storage layoutStruct) internal view {
    if (!_isOperator(layout, msg.sender) &&
        !_isFunctionOperator(layout, msg.sig, msg.sender)) {
        revert IOperable.NotOperator(msg.sender);
    }
}

function _onlyOperator() internal view {
    _onlyOperator(_layoutStruct());
}

All policy lives in the guard. There is a single source of truth for the condition.

Modifiers

abstract contract OperableModifiers {
    modifier onlyOperator() {
        OperableRepo._onlyOperator();
        _;
    }
}

Modifiers contain no logic. They exist only to provide the modifier syntax for contracts that inherit them.

Direct Calls

Because the guard is a regular internal function, other Repo functions or Targets can call _onlyOperator() directly without going through a modifier. This is the preferred pattern inside library code.

Function-Level Operators

The operable pattern supports both global operators and per-function operators. The guard checks both. Packages and facets that install operable logic automatically receive this granularity.

Reentrancy Guards

Reentrancy protection uses transient storage (EIP-1153). The lock is acquired and released within the same transaction and does not require storage writes that persist.

See the reentrancy module for the concrete ReentrancyLockRepo and ReentrancyLockModifiers implementation.

DFPkg Pattern

A Diamond Factory Package (DFPkg) is a contract that implements IDiamondFactoryPackage. It packages facet references and the logic needed to initialize a new Diamond proxy instance.

Packages are how Crane turns reusable facets into deployable products: one package definition produces consistent proxies at predictable addresses when given the same arguments.

Why packages exist

  • Composition: A package lists which facets (and selectors) a proxy needs.
  • Initialization: initAccount runs via delegatecall on the new proxy to set storage.
  • Determinism: calcSalt + CREATE3-style factory flows yield stable addresses.
  • Reuse: Facets are deployed once; packages only reference them. That is the security and gas story: reuse already deployed and verified code via facets attached through DFPkgs (“deploy once, attach everywhere”).

Interface-owned structs (critical rule)

PkgInit and PkgArgs must be defined on the interface (I*DFPkg), never only on the implementing contract. That enables typed IMyDFPkg.PkgInit usage in FactoryServices and abi.encode call sites.

interface IERC20DFPkg {
    struct PkgInit { /* facet addresses for constructor */ }
    struct PkgArgs { /* per-instance deploy args */ }
}

contract ERC20DFPkg is IERC20DFPkg, IDiamondFactoryPackage {
    // constructor(PkgInit memory pkgInit) { ... }
}

Core package surface

Typical DFPkg responsibilities:

FunctionRole
packageName()Human/registry name
facetCuts()IDiamond.FacetCut[] for the proxy
diamondConfig()Diamond configuration blob
calcSalt(bytes)Deterministic salt from package args
initAccount(bytes)Delegatecalled on the new proxy
postDeploy(address)Hook after proxy exists

Central NatSpec selectors (examples already used in deployment docs):

  • packageName()0xabc8b346
  • facetCuts()0xa4b3ad35
  • initAccount(bytes)0x870d4838
  • postDeploy(address)0x70068fcf

Conceptual vs operational docs

DocFocus
This pageWhat a DFPkg is, struct rules, package lifecycle concepts
Diamond Factory PackagesOperational package construction and deploy steps
CREATE3 & New Chain SetupFactories, chain bootstrap, DPCF reuse
RegistriesHow packages/facets are discovered after deploy

Lifecycle (high level)

  1. Deploy facets once with Create3Factory (registered automatically when using factory deploy helpers).
  2. Deploy a package whose constructor stores immutable facet addresses (PkgInit).
  3. Deploy proxies via DiamondPackageCallBackFactory.deploy(pkg, pkgArgs) — the package supplies cuts + init.
  4. Consumers resolve packages later via Package Registry canonicalPackage(interfaceId) when available.

See also

Registries

Crane registries provide on-chain discovery, canonical resolution, and configuration for facets, packages, and call targets. They are core to agent-proof reuse: deploy once via CREATE3, register, then resolve with canonical* instead of hardcoding addresses or redeploying bytecode.

This enables “deploy once, attach everywhere” (see Getting Started for the security and gas rationale).

Chain setup note

To stand up a chain presence, deploy your own Create3Factory with the CREATE3 package (Create3FactoryDFPkg). The Diamond Package Factory (DiamondPackageCallBackFactory, interface id 0x949da331) does not need to be redeployed per chain — it is safe and intended for public reuse across deployments. See CREATE3 & New Chain Setup.

Facet Registry

Location: contracts/registries/facet/

Purpose: Track deployed IFacet implementations. Lookup by name, interface id, or function selector. Resolve preferred implementations with canonicalFacet(bytes4 interfaceId).

Population: Auto-populated on successful deployFacet* through Create3Factory (after CREATE3, factory calls facet.facetMetadata() and registers). Manual registerFacet / setCanonicalFacet also exist.

Consumer usage:

IFacet cut = IFacetRegistry(address(factory)).canonicalFacet(type(IDiamondCut).interfaceId);

Also: allFacets(), facetsOfName, facetsOfInterface, facetsOfFunction, metadata getters.

IFacet metadata selectors (documented elsewhere in Crane): facetName 0x5b6f4d01, facetInterfaces 0x2ea80826, facetFuncs 0x574a4cff, facetMetadata 0xf10d7a75.

Package Registry

Location: contracts/registries/package/ (DiamondFactoryPackageRegistry)

Purpose: Track IDiamondFactoryPackage deployments. Canonical package per interface for deterministic proxy construction.

Population: Auto on deployPackage* via Create3Factory (package.packageMetadata() then register). Also registerPackage / setCanonicalPackage.

Consumer usage: canonicalPackage(interfaceId), allPackages(), lookups by name/interface/facet.

Used in bootstrap (InitDevService) when deploying Create3 DFPkg, CallTarget DFPkg, BountyBoard DFPkg, etc.

Call Target Registry

Location: contracts/registries/target/ (query + management facets; CallTargetRegistryDFPkg)

Purpose: Dynamic configuration oracle for call targets — default or per-caller target for an interface id. Lets routers/proxies resolve “who do I call?” without baking addresses into bytecode.

Population: Explicit via management (setDefaultCallTargetForID, per-caller setters). Not auto-filled on facet deploy.

Usage: defaultCallTargetForID, callTargetForIDForCaller (query interface).

How registries attach to Create3Factory

Registry facets are deployed with Create3 (salts from type names) and attached to the Create3Factory diamond during bootstrap. After factory/package deploys, tests should assert expected registry entries (production-first; see Testing Patterns).

Agent workflow

  1. Bootstrap factories (CraneTest / InitDevService).
  2. Deploy facets/packages through Create3 helpers (auto-register).
  3. Resolve with canonicalFacet / canonicalPackage in DFPkg constructors and init paths.
  4. Assert registry state in tests with Behavior libraries where available.

Skills: crane-deployment, crane-architecture. Repo guide: AGENTS.md.

See also

CREATE3 Factory

Create3Factory provides deterministic deployment of arbitrary contracts using CREATE3 semantics. It is the foundation for facet and package reuse.

flowchart TB
    subgraph Factories["Deployment Infrastructure"]
        C3["Create3Factory<br/>(deploys facets + packages)"]
        DPCF["DiamondPackageCallBackFactory<br/>(deploys Diamond proxies)"]
    end

    C3 -->|deploys once| Facets["Facets<br/>(ERC20Facet, DiamondCutFacet, ...)"]
    C3 -->|deploys once| Packages["DFPackages<br/>(ERC20DFPkg, ...)"]
    Packages -->|references| Facets
    DPCF -->|uses| Packages
    DPCF -->|deploys many| Proxies["Diamond Proxies<br/>(one per instance)"]

    classDef core fill:#2c3e50,stroke:#80cbc4,color:#ecf0f1
    class C3,DPCF core

Critical reuse note (LR-2): The DiamondPackageCallBackFactory (the DPCF, implementing IDiamondPackageCallBackFactory) is deployed once per ecosystem/setup. It does not need to be redeployed per chain or per consumer. It is safe and intended for public reuse across all deployments and chains.

Consumers obtain its address from a Create3Factory (via diamondPackageFactory() selector 0x0fe96d13).

/// @custom:signature diamondPackageFactory()
/// @custom:selector 0x0fe96d13
function diamondPackageFactory() external view returns (IDiamondPackageCallBackFactory factory);

Deploying your own Create3Factory (via its DFPkg) is how you bootstrap a new chain presence; the callback factory is shared (interface ID 0x949da331 from central values).

/// @custom:interfaceid 0x949da331
interface IDiamondPackageCallBackFactory { /* ... */ }

From implementation:

@dev Deployed once via Create3Factory (see Create3Factory.diamondPackageFactory()). Safe and intended for reuse by any consumer on any chain.

This factory is intended to be deployed once per ecosystem and reused across chains/consumers.

See also docs/deployment/dfpkg.md. This directly enables amortized deployment costs and security-through-reuse (verified code reused via facets; no need to re-deploy logic).

Guarantees

Guarantees

  • Address depends only on deployer, salt, and creation code (for packages with constructor arguments, the init data is included in the package deployment).
  • Same inputs produce the identical address on every EVM chain.
  • Facets and packages are deployed once and referenced by address thereafter.

Setting Up a Chain Presence: CREATE3 Package (Create3FactoryDFPkg)

LR-2 requirement: Use the Create3FactoryDFPkg (implements ICREATE3DFPkg extending IDiamondFactoryPackage) to deploy your own Create3Factory Diamond for a new chain or isolated environment.

PkgInit and PkgArgs must be defined on the interface (see ICREATE3DFPkg):

// tag::PkgInit-create3[]
interface ICREATE3DFPkg is IDiamondFactoryPackage {
    struct PkgInit {
        IFacet diamondCutFacet;
        IFacet multiStepOwnableFacet;
        IFacet operableFacet;
        IFacet create3FactoryFacet;
        IFacet facetRegistryFacet;
        IFacet packageRegistryFacet;
        IFacet callTargetRegistryQueryFacet;
        IFacet callTargetRegistryManagementFacet;
        IDiamondPackageCallBackFactory diamondFactory;  // the reusable one (see above)
    }

    struct PkgArgs {
        address owner;
    }
    // ...
}
// end::PkgInit-create3[]

Key central NatSpec values (use ONLY these; from CENTRALLY_COMPUTED_NATSPEC_VALUES.md):

  • packageName() : 0xabc8b346
  • facetInterfaces() : 0x2ea80826
  • facetAddresses() : 0x52ef6b2c
  • facetCuts() : 0xa4b3ad35
  • diamondConfig() : 0x65d375b3
  • calcSalt(bytes) : 0xd82be56e
  • initAccount(bytes) : 0x870d4838
  • postDeploy(address) : 0x70068fcf
  • deployCreate3Factory(address) : 0x34cb11b5

Bootstrap Flow for New Chain

  1. Obtain/deploy an initial Create3Factory entrypoint (bare new Create3Factory{salt}(owner) as in InitDevService.initFactory).
  2. Use bootstrap to deploy canonical core facets (Cut, Ownable, Operable, Create3 facet, registry facets, etc.).
  3. Wire the reusable DiamondPackageCallBackFactory (see reuse note; selector 0x1cdca5df for set).
  4. Deploy (or obtain) the Create3FactoryDFPkg passing the facets + the shared diamondFactory in PkgInit.
  5. Call deployCreate3Factory(owner) on the DFPkg (internally delegates to diamondFactory.deploy using PkgArgs):
// Example (selectors from central values)
ICREATE3DFPkg.PkgInit memory pkgInit = ICREATE3DFPkg.PkgInit({
    diamondCutFacet: IFacetRegistry(...).canonicalFacet(type(IDiamondCut).interfaceId),
    multiStepOwnableFacet: ...,
    operableFacet: ...,
    create3FactoryFacet: ...,
    facetRegistryFacet: ...,
    packageRegistryFacet: ...,
    callTargetRegistryQueryFacet: ...,
    callTargetRegistryManagementFacet: ...,
    diamondFactory: diamondFactory  // reusable, obtained via create3Factory.diamondPackageFactory() (0x0fe96d13)
});

Create3FactoryDFPkg pkg = Create3FactoryDFPkg( /* deployed via create3 or new for bootstrap */ );

// Deploy your chain's Create3Factory Diamond (deterministic via callback factory)
ICreate3FactoryProxy myFactory = pkg.deployCreate3Factory(owner);
// Internally uses: DIAMOND_FACTORY.deploy(SELF, abi.encode(PkgArgs({owner: owner})))
// (deploy selector 0xe97fac05 on IDiamondPackageCallBackFactory)

See full implementation in contracts/factories/create3/Create3FactoryDFPkg.sol (includes facetCuts(), initAccount which calls MultiStepOwnableRepo._initialize, packageMetadata() etc.). calcSalt hashes the pkgArgs.

After, use myFactory for further deploys; registries are available on it.

This is how InitDevService and InitBcService stand up environments. See CraneTest for test usage.

Cross-reference: docs/deployment/dfpkg.md (general DFPkg + reuse), contracts/InitDevService.sol, AGENTS.md (Diamond Package Deployment Pattern).

Registries Explanation

The Create3Factory system (and DFPkgs) automatically populates three registries on every deploy. Registries live as facets on the Create3Factory Diamond.

Facet Registry (IFacetRegistry)

Purpose: Track deployed facets by name, interface, and function selectors. Enables lookup of canonical implementations instead of hardcoding addresses in every PkgInit.

Key methods (central selectors on related surfaces use 0x2ea80826 for facetInterfaces patterns):

  • canonicalFacet(bytes4 interfaceId) returns (IFacet)
  • facetsOfInterface(bytes4)
  • allFacets(), registerFacet(...), setCanonicalFacet(...)
  • deployFacet(...) / deployCanonicalFacet* (these auto-register)

Population: Create3Factory._registerFacet calls FacetRegistryRepo using facet.facetMetadata() (from IFacet with facetName() 0x5b6f4d01, facetInterfaces() 0x2ea80826, facetFuncs() 0x574a4cff).

Consumers: Packages and FactoryServices resolve e.g. IFacetRegistry(address(factory)).canonicalFacet(type(IDiamondCut).interfaceId).

Diamond Factory Package Registry (IDiamondFactoryPackageRegistry)

Purpose: Track DFPkgs (by name, interfaces, constituent facets) for discovery and canonical resolution.

Key methods:

  • canonicalPackage(bytes4 interfaceId)
  • deploy*Package* variants (auto-register via packageMetadata())
  • registerPackage(...), setCanonicalPackage(...)

Population: Automatic in Create3Factory._registerPackage after deployPackage*.

Call Target Registry (Query + Management)

Purpose: Controls default and per-caller allowed external call targets (used by metatx/relayer patterns and ICallTargetRegistry*).

Populated/used via the facets installed by Create3FactoryDFPkg (and CallTargetRegistryDFPkg).

See contracts/registries/facet/IFacetRegistry.sol, contracts/registries/package/IDiamondFactoryPackageRegistry.sol, and ICallTargetRegistry* interfaces. Query from any Create3Factory instance.

Consumers interact via the registry facets exposed on Create3Factory (no separate deployment needed once bootstrapped).

Protocol integrations and tests rely on the factories + CraneTest.

Inheritance pattern (see contracts/test/CraneTest.sol):

import {CraneTest} from "@crane/contracts/test/CraneTest.sol";

abstract contract TestBase_MyProtocol is CraneTest {  // or other TestBase_*
    function setUp() public virtual override {
        CraneTest.setUp();  // calls InitDevService.initEnv -> wires create3Factory + diamondPackageFactory + registries
        // ...
    }
}

InitDevService.initEnv(address(this)) deploys canonicals under deterministic salts and wires diamondPackageFactory.

Example protocol test bases (cross-links):

  • Camelot V2: contracts/protocols/dexes/camelot/v2/test/bases/TestBase_CamelotV2.sol (inherits Weth9 + setup)
  • Balancer V3 Vault: contracts/protocols/dexes/balancer/v3/test/bases/TestBase_BalancerV3Vault.sol (inherits CraneTest + VaultContractsDeployer)
  • Reliquary: contracts/protocols/staking/reliquary/v1/test/bases/TestBase_Reliquary.sol
  • See test/foundry/spec/protocols/dexes/balancer/v3/pool-constProd/BalancerV3ConstantProductPoolDFPkg_Integration.t.sol for create3Factory.deployFacet + diamondFactory.deploy(pkg, pkgArgs) usage + registry checks.

In tests, after setUp:

// Deploy via Create3
IFacet myFacet = create3Factory.deployFacet(
    type(MyFacet).creationCode,
    abi.encode(type(MyFacet).name)._hash()
);

// Deploy proxy via reusable DPCF
address proxy = diamondFactory.deploy(pkg, abi.encode(pkgArgs));  // selector 0xe97fac05

Protocol utilities used in these tests:

  • Constant product math: ConstProdUtils (see contracts/utils/math/ConstProdUtils.sol and tests under test/foundry/spec/utils/math/constProdUtils/)
  • DEX-specific services: CamelotV2Service, Balancer router helpers, etc. (in contracts/protocols/dexes/*/services/)
  • Stubs for mocks: in protocol stubs/ and test bases.
  • General type libs (used across): AddressSetRepo, Bytes32SetRepo etc. (see contracts/utils/collections/ and Repos).

See crane-testing patterns, AGENTS.md TestBase/Behavior sections, and docs/protocols/* for integration details. Tests assert determinism, registry population, and use Behavior_* libs for interface compliance (e.g. Behavior_IFacet).

This cross-linking ensures protocol ports reuse the CREATE3 chain bootstrap and shared DPCF without duplication.

Core Methods

deploy

Deploys any contract (see ICreate3Factory.create3).

/// @custom:signature create3(bytes,bytes32)
/// @custom:selector 0xa7b62a7f
address deployed = create3Factory.create3(creationCode, salt);

deployFacet

Convenience for facets (no constructor arguments).

IFacet facet = create3Factory.deployFacet(
    type(MyFacet).creationCode,
    abi.encode(type(MyFacet).name)._hash()
);

deployPackageWithArgs

Deploys a package that requires constructor arguments (typically immutable facet references).

/// @custom:signature create3WithArgs(bytes,bytes,bytes32)
/// @custom:selector 0x1f7fe4db
address pkg = create3Factory.deployPackageWithArgs(
    type(MyDFPkg).creationCode,
    abi.encode(IMyDFPkg.PkgInit({ facet: facetAddress })),
    salt
);

Also see diamondFactory.deploy(pkg, pkgArgs) (selector 0xe97fac05) and calcAddress (0x33a41d70) on the reusable callback factory.

Salt Convention

Salts are produced from the contract type name:

using BetterEfficientHashLib for bytes;

bytes32 salt = abi.encode(type(MyContract).name)._hash();

This convention produces stable, human-readable salts and prevents accidental collisions between unrelated contracts.

Canonical Deployment in Tests and Scripts

InitDevService.initEnv deploys the full set of core facets and both factories under deterministic salts. It also wires registries so that canonical facets for common interfaces can be retrieved by interface ID.

All core facets (DiamondCut, MultiStepOwnable, Operable, ERC165, Loupe, etc.) are deployed exactly once per environment and reused by every package and proxy created in that environment.

Registry Integration

See the detailed "Registries Explanation" section above for purpose, population mechanics (via _register* in Create3Factory), and consumer usage (canonicalFacet etc. via interface ID). The factory system maintains facet and package registries. Packages and higher-level services can resolve the canonical facet for a given interface instead of passing addresses explicitly in every PkgInit. Registries are exposed as facets on bootstrapped Create3Factory instances (installed via Create3FactoryDFPkg).

See also

Diamond Factory Packages (DFPkg)

A DFPkg is a contract that implements IDiamondFactoryPackage. It packages a set of facet references and the logic required to initialize a new Diamond proxy.

Value

  • Facets are deployed separately and referenced by address.
  • A package declares exactly which facets and which functions are installed.
  • Initialization (initAccount) and post-deployment hooks are executed inside the deployment transaction via delegatecall.
  • The same package + arguments always produce a proxy at the same address.

Because facets are immutable references inside the package, every proxy created from the package shares the identical logic implementations.

The DiamondPackageCallBackFactory that executes DFPkg deployments (via pkg.deploy(diamondFactory, args)) is deployed once and reused across chains and projects. You obtain it from your Create3Factory; you do not deploy a new callback factory for each chain or DFPkg use.

Interface

interface IDiamondFactoryPackage {
    struct DiamondConfig {
        IDiamond.FacetCut[] facetCuts;
        bytes4[] interfaces;
    }

    function packageName() external view returns (string memory);
    function facetCuts() external view returns (IDiamond.FacetCut[] memory);
    function diamondConfig() external view returns (DiamondConfig memory);
    function calcSalt(bytes memory pkgArgs) external view returns (bytes32);
    function initAccount(bytes memory initArgs) external;
    function postDeploy(address account) external returns (bool);
    // ...
}

Typical Package Structure

Important: PkgInit and PkgArgs must be defined inside the I*DFPkg interface (not the contract implementation). This allows type-safe references like IMyDFPkg.PkgInit from FactoryServices and callers.

See the crane-architecture skill references/dfpkg-pattern.md for the full rule and the frequent error of defining them on the contract.

interface IERC20DFPkg {
    struct PkgInit {
        IFacet erc20Facet;   // constructor argument
    }
    struct PkgArgs {
        string name;
        string symbol;
        uint8 decimals;
        uint256 totalSupply;
        address recipient;
    }
}

contract ERC20DFPkg is IERC20DFPkg, IDiamondFactoryPackage {
    IFacet immutable ERC20_FACET;

    constructor(PkgInit memory pkgInit) {
        ERC20_FACET = pkgInit.erc20Facet;
    }

    function facetCuts() public view returns (IDiamond.FacetCut[] memory cuts) {
        cuts = new IDiamond.FacetCut[](1);
        cuts[0] = IDiamond.FacetCut({
            facetAddress: address(ERC20_FACET),
            action: IDiamond.FacetCutAction.Add,
            functionSelectors: ERC20_FACET.facetFuncs()
        });
    }

    function initAccount(bytes memory initArgs) external {
        // decode pkgArgs and call ERC20Repo._initialize etc.
    }

    function calcSalt(bytes memory pkgArgs) public pure returns (bytes32) {
        // usually keccak256(abi.encode(pkgArgs)) or similar
    }
}

Deployment Flow (via DiamondPackageCallBackFactory)

sequenceDiagram
    participant User
    participant Factory as DiamondPackageCallBackFactory
    participant Pkg as IDiamondFactoryPackage
    participant Proxy as MinimalDiamondCallBackProxy

    User->>Factory: deploy(pkg, pkgArgs)
    Factory->>Pkg: calcSalt(pkgArgs)  [delegatecall]
    Pkg-->>Factory: salt
    Factory->>Factory: compute CREATE2 address
    alt proxy already exists
        Factory-->>User: return existing proxy
    else proxy does not exist
        Factory->>Pkg: updatePkg / store context
        Factory->>Proxy: CREATE2 deploy (MinimalDiamondCallBackProxy)
        Proxy->>Factory: initAccount callback (delegatecall)
        Factory->>Pkg: diamondConfig() + initAccount(args)  [delegatecall on proxy]
        Pkg->>Proxy: perform storage initialization
        Factory->>Pkg: postDeploy(proxy)
        Pkg-->>Factory: success
        Factory->>Proxy: remove temporary post-deploy hook (if any)
        Factory-->>User: proxy address
    end

The detailed original sequence is also present as NatSpec in contracts/factories/diamondPkg/IDiamondFactoryPackage.sol.

Numbered Steps

  1. Caller invokes factory.deploy(pkg, pkgArgs).
  2. Factory calls pkg.calcSalt(pkgArgs) to obtain the CREATE2 salt for the proxy.
  3. If a proxy already exists at the computed address, it is returned immediately.
  4. Otherwise the factory deploys a MinimalDiamondCallBackProxy via CREATE2.
  5. The proxy calls back into the factory.
  6. The factory stores the package and arguments, then delegatecalls pkg.initAccount(processedArgs) on the proxy.
  7. The package performs all storage initialization (setting owners, writing token metadata, etc.).
  8. The factory calls pkg.postDeploy(proxy).
  9. Optional post-deploy hook facet is removed.
  10. The proxy address is returned.

The package never holds proxy state. It only supplies cuts and performs initialization.

Reuse Characteristics

  • One ERC20Facet deployment serves every ERC20 proxy created from ERC20DFPkg on that chain.
  • The facet address is identical on every chain when deployed with the same salt.
  • Adding a new feature requires only a new facet deployment and a new or updated package. Existing proxies are unaffected unless upgraded.
  • Cross-chain deployment scripts can compute addresses in advance and verify that the expected facets and packages already exist at those addresses.

Helper Methods on Packages

Many packages expose convenience deploy(...) functions that encode arguments and forward to the factory:

IERC20 token = erc20Pkg.deploy(
    diamondFactory,
    "Example",
    "EX",
    18,
    1_000_000e18,
    recipient,
    bytes32(0)
);

Post-Deploy Hooks

Packages may install a temporary PostDeployAccountHookFacet during initialization. After postDeploy returns, the factory removes the hook facet. This provides a safe window for privileged one-time setup actions.

Application / Consumer Layers

Crane's DFPkg + factory primitives are general. Some projects add registry or manager facades on top for registration, discovery, and access control of certain package types. Those additional rules and entry points are the responsibility of the consuming application — see the consumer's documentation.

See also

Factory Services

FactoryService libraries group the deployment of related facets and packages.

Purpose

  • Centralize salt constants and deployment ordering.
  • Apply consistent labeling for traces (vm.label).
  • Encapsulate constructor argument construction for packages.
  • Provide a single place to update when new core facets are added.

Example Pattern

library AccessFacetFactoryService {
    using BetterEfficientHashLib for bytes;

    Vm constant vm = Vm(VM_ADDRESS);

    function deployMultiStepOwnableFacet(ICreate3Factory factory)
        internal
        returns (IFacet)
    {
        IFacet facet = factory.deployFacet(
            type(MultiStepOwnableFacet).creationCode,
            abi.encode(type(MultiStepOwnableFacet).name)._hash()
        );
        vm.label(address(facet), type(MultiStepOwnableFacet).name);
        return facet;
    }

    function deployOperableFacet(ICreate3Factory factory)
        internal
        returns (IFacet)
    {
        IFacet facet = factory.deployFacet(
            type(OperableFacet).creationCode,
            abi.encode(type(OperableFacet).name)._hash()
        );
        vm.label(address(facet), type(OperableFacet).name);
        return facet;
    }
}

Similar services exist for introspection facets and for full DFPkg deployment sequences.

Usage in Tests and Scripts

IFacet msOwnable = AccessFacetFactoryService.deployMultiStepOwnableFacet(create3Factory);
IFacet operable = AccessFacetFactoryService.deployOperableFacet(create3Factory);

// later, when constructing a package that needs them:
SomePkg pkg = ... (deployPackageWithArgs(..., abi.encode(PkgInit({
    multiStepOwnableFacet: msOwnable,
    operableFacet: operable,
    ...
}))));

Benefits for Reuse

All consumers resolve to the same canonical facet addresses. When a package is constructed with these facets, every proxy it creates references the identical implementations. Updating a facet requires only redeploying the facet (new salt or new versioned package) and updating packages that depend on it.

See also

BattleChain Security Gate

Before promoting Crane core factories, DFPkgs, or significant protocol ports to Base or mainnet, they must survive BattleChain.

Why BattleChain

BattleChain (chain ID 627 testnet / 626 mainnet) provides adversarial "attack mode" with whitehats and automated tools. It is the required quality bar for anything that will be reused by many agents and projects.

See AGENTS.md and:

  • Pilot: scripts/foundry/Script_Pilot_BC_ERC20Permit.s.sol
  • IndexedEx launch promo (Wave A): scripts/foundry/Script_Promo_BC_Launch.s.sol — Crane core + ERC20Permit + Uni V2/V4 + Permit2 + Safe Harbor; use BC-provided WETH + Uni V3
  • Balancer V3 (Wave B): scripts/foundry/Script_Promo_BC_BalancerV3.s.sol — Vault + Router diamonds + Weighted/Stable/ConstProd pool DFPkgs; binds Wave A factories + BC WETH/Permit2. Plan: docs/superpowers/plans/2026-07-23-bc-balancer-v3-wave-b.md. X: docs/roadmap/X_BC_BALANCER_V3.md
  • Consumer plan: IndexedEx docs/BATTLECHAIN_LAUNCH_PROMO.md

Practice: use BattleChain-provided contracts

Do not redeploy or replace anything BattleChain already ships (testnet mock/dependency contracts or genesis infrastructure). Bind to their addresses and only create3-deploy Crane-owned surfaces that are missing.

Provided on BC testnet (examples)Wave A action
WETH 0x4CAc…1f42Use
Uniswap V3 Factory / SwapRouter / NPMUse
USDC, DAI, Chainlink mocks, Safe, …Use when needed
Uni V2, Uni V4 PoolManager, Permit2, Crane factoriesDeploy via Crane create3

Source: BattleChain mock & dependency contracts.

Process

  1. Implement + test locally (Foundry invariants, behavior tests).
  2. Use InitBcService (BattleChain-aware bootstrap) in deployment scripts.
  3. Deploy to BattleChain testnet.
  4. Create Safe Harbor agreement (scope the Create3Factory with appropriate child contract scope).
  5. Enable attack mode and monitor.
  6. Survive testing → promote to PRODUCTION on BattleChain.
  7. Re-deploy identical bytecode (same salts) to Base / target mainnets.

Only after this gate do we consider a component "production-grade" for the shared framework.

For Agents

When an agent is asked to port or build core infrastructure:

  • Follow the gate explicitly.
  • Document pilot scripts and results.
  • Update this page and AGENTS.md in consumer repos.

Failure to respect the gate risks fund loss for users of reusable packages.

References

  • contracts/InitBcService.sol
  • BattleChain docs (LLM-friendly): https://docs.battlechain.com/llms-full.txt
  • Pilot examples in scripts/

Deployed addresses

Wave A is live on BattleChain testnet (block 17158).

The promo script refreshes the JSON/table on re-broadcast.

See also

Deployed Addresses

Canonical on-chain deployments of Crane core and vendored protocol surfaces.

Source of truth (machine-readable): files under addresses/.
Solidity constants: contracts/constants/networks/BC_TESTNET.sol
Human tables: generated markdown included below (do not hand-edit generated tables).

After a successful broadcast of scripts/foundry/Script_Promo_BC_Launch.s.sol, the script overwrites:

FilePurpose
addresses/battlechain-sepolia.jsonJSON for agents / tooling
addresses/battlechain-sepolia.table.mdmdBook include table

Agent handoff: when the operator says Wave A is deployed, read the JSON, confirm non-zero addresses, ensure this page reflects the table include, and update status / notes if needed.


BattleChain Testnet (chain 627)

FieldValue
Network aliasbattlechain-sepolia
Chain ID627
RPChttps://testnet.battlechain.com
Explorerexplorer.testnet.battlechain.com
Deploy scriptscripts/foundry/Script_Promo_BC_Launch.s.sol
WaveA — Crane core + ERC20Permit + Uni V2/V4 + Permit2 + Safe Harbor
PolicyUse BC-provided deps; do not replace (WETH, Uni V3, …)
Deployer EOA0xF71ea560c6465727efFe07Cfb4e1a05B40520Dd7
Deployed at block17158
StatusDeployed (Wave A live)

Addresses (Wave A)

ComponentAddress
Create3Factory (core)0x0792632343b6a31e4606452aeb50F18A2DE14c27
DiamondPackageCallBackFactory0x12C636556d0e5e5c0f2006583E32AcA49c8d4Ffb
ERC20Facet0x3547a3e83E88c6b080b2d3202a3c3e062A43b196
ERC5267Facet0xe09934dA5103B49D59d67DE904bda7CBd3fB07E8
ERC2612Facet0x8C52034D050D81807d77de91251d14BbE9AA7Fac
ERC20PermitDFPkg0xDeE300dc0708DeD0517f54e70Ac2ddfDaAE3142A
Sample permit token (CBCG)0xe6513392dCB82De2D0Ec345DDd8bbF7fEd836EE7
WETH (BC-provided)0x4CAc28Fc96bb8fa0e6F94ef0E579384902142f42
Uniswap V2 Factory (Crane)0xA5511E60547Bfe2B9d9ED1d25451A334a0389D82
Uniswap V2 Router02 (Crane)0x2E8825338CDfdFa5e7E39031C6cAcBC47cAbd294
Uniswap V3 Factory (BC-provided)0xd5DCFCab1B60C70F45D61597b351674b4b3C8CDc
Uniswap V3 SwapRouter (BC-provided)0x4FC93149e329C15BfF627E967aaA487079D89d2F
Uniswap V3 NPM (BC-provided)0x43d314e63223041C61460c9A2F5e597Ff7D1cd30
Uniswap V4 PoolManager (Crane)0xd88B78f1b9A1DC78D0D4ef1cDdAB318c179E9267
BetterPermit2 (Crane)0x6f52280B6d2DA1159d4176559e958d0A603C95CD
Safe Harbor agreement0xEA85951dA0600989CbbCAbd5CB98212c57410F55

Solidity import

import {BC_TESTNET} from "@crane/contracts/constants/networks/BC_TESTNET.sol";

// Crane Wave A
address factory = BC_TESTNET.CREATE3_FACTORY;
address uniV2 = BC_TESTNET.UNISWAP_V2_FACTORY;

// BattleChain-provided (do not redeploy)
address weth = BC_TESTNET.WETH;
address uniV3 = BC_TESTNET.UNISWAP_V3_FACTORY;

Notes

  • Safe Harbor root is Create3Factory (0xC8E9…AD3A) with ChildContractScope.All — protocol stubs and diamonds are children by deployer lineage. Agreement: 0xC0C1…08f1.
  • Sample permit token (CBCP) is a demo ERC20Permit diamond; it is not mainnet RICH.
  • WETH and Uniswap V3 (factory, SwapRouter, NPM) are BattleChain-provided — Wave A binds to them and does not redeploy.
  • Crane create3-deploys Uni V2, Uni V4 PoolManager, Permit2, and Crane core when not provided by BC.
  • Canonical BC infrastructure + test tokens + mocks also live in BC_TESTNET for ready use.

Operator broadcast (from Crane root)

export DEPLOYER=$(cast wallet address --account deployer)

forge script scripts/foundry/Script_Promo_BC_Launch.s.sol:Script_Promo_BC_Launch \
  --rpc-url battlechain-sepolia \
  --broadcast \
  --skip-simulation \
  --account deployer \
  --sender $DEPLOYER \
  -vv

Then commit updated docs/deployment/addresses/battlechain-sepolia.* and contracts/constants/networks/BC_TESTNET.sol.


Other networks

No Base / Ethereum mainnet Crane deployment address book yet. Add a new JSON + table under addresses/ and a section here when those land.

BattleChain mainnet (626) infrastructure constants: contracts/constants/networks/BC_MAIN.sol (no Crane Wave A deploy there yet).


See also

Code Style

Crane enforces a strict set of conventions to keep large Diamond codebases consistent and auditable.

Section Headers

Major sections use 78-character blocks:

/* -------------------------------------------------------------------------- */
/*                             Section Name                                   */
/* -------------------------------------------------------------------------- */

Subsections use the shorter form:

/* ------ Feature Name ------ */

Imports

Group in this order:

  1. External libraries (@openzeppelin, @solady).
  2. Crane interfaces (@crane/contracts/interfaces/...).
  3. Crane contracts (@crane/contracts/...).
  4. Test utilities (only in test files).

Use the defined remappings:

  • @crane/
  • @solady/
  • @openzeppelin/
  • forge-std/

Function Order

Within each contract or library:

  1. Constructor
  2. Receive / Fallback
  3. External
  4. Public
  5. Internal
  6. Private

Naming

ElementConventionExample
Storage access_layoutStruct()_layoutStruct(), _layoutStruct(bytes32)
Initialization_initialize(...)_initialize(address owner_)
Internal state functions_functionName(...)_isOperator(address)
Guard functions_onlyXxx(...)_onlyOperator()
ModifiersonlyXxxonlyOperator
Storage parameterlayoutStructStorage storage layoutStruct
All parameterstrailing underscoreowner_, amount_

Parameters always end with _ to prevent shadowing of state or storage variables.

Storage Slot Names

Hierarchical and deterministic:

  • Crane internals: crane.{domain}.{feature}
  • ERC standards: eip.erc.{number}
  • Protocols: protocols.{category}.{name}.{version}.{concern}

Example:

bytes32 internal constant STORAGE_SLOT =
    keccak256(abi.encode("crane.access.operable"));

Compilation Rules

  • viaIR and via_ir must remain disabled.
  • Stack-too-deep errors are resolved by grouping parameters and intermediate values into struct types passed by memory or calldata.
  • Optimizer runs are set to 1 to respect contract size limits under the Diamond pattern.

Reference

See contracts/StyleGuide.sol for the canonical template.

NatSpec and Documentation Standard

Crane combines NatSpec comments with AsciiDoc include-tags to keep documentation accurate and extractable. This standard is defined by LR-1 in PRD.md and aligns with AGENTS.md.

Canonical Gold Standard (required quality and format):

  • contracts/access/ERC8023/IMultiStepOwnable.sol
  • contracts/access/ERC8023/MultiStepOwnableTarget.sol
  • contracts/access/ERC8023/MultiStepOwnableRepo.sol
  • contracts/access/ERC8023/MultiStepOwnableFacet.sol

Follow these files exactly for include-tag style, custom tags, rich NatSpec (@notice, @param, @return, @custom:emits, @custom:throws), and overload tagging.

Include Tags

Wrap every documented symbol. Tag names must match the symbol exactly (no extra spaces inside []).

Use hyphen-separated parameter types (no spaces, param names omitted) for disambiguation, especially overloads and events:

// tag::initiateOwnershipTransfer(address)[]
/**
 * @notice Initiates a ownership transfer by storing `newOwner` as the pending owner.
 * @param newOwner The address to which to initiate a ownership transfer.
 * @custom:selector 0xc0b6f561
 * @custom:signature initiateOwnershipTransfer(address)
 * @custom:emits OwnershipTransferInitiated(address,address)
 * @custom:throws NotOwner(address)
 */
function initiateOwnershipTransfer(address newOwner) external;
// end::initiateOwnershipTransfer(address)[]

// tag::OwnershipTransferInitiated(address-address)[]
/**
 * @notice Emitted when an ownership transfer is initiated.
 * @param prevOwner The address initiating the transfer. Will only be current owner.
 * @param newOwner The address to which ownership is being transferred.
 * @custom:topiczero 0xb150023a879fd806e3599b6ca8ee3b60f0e360ab3846d128d67ebce1a391639a
 */
event OwnershipTransferInitiated(address indexed prevOwner, address indexed newOwner);
// end::OwnershipTransferInitiated(address-address)[]

// tag::_layoutStruct(bytes32)[]
/**
 * @dev Argumented version of _layoutStruct to allow for custom storage slot usage.
 * @param slot Storage slot to bind to the Repo's Storage struct.
 * @return layoutStruct The bound Storage struct.
 */
function _layoutStruct(bytes32 slot) internal pure returns (Storage storage layoutStruct);
// end::_layoutStruct(bytes32)[]

// tag::_layoutStruct()[]
/**
 * @dev Default version of _layoutStruct binding to the standard STORAGE_SLOT.
 * @return layoutStruct The bound Storage struct.
 */
function _layoutStruct() internal pure returns (Storage storage layoutStruct);
// end::_layoutStruct()[]

The markers must be exact. Wrap the entire declaration (including NatSpec) between the tags.

Custom Tags

Symbol TypeTagValue TypeExample Computation
Function@custom:signaturestringSee Verification Script / Central Values
Function@custom:selectorbytes4See Verification Script / Central Values
Error@custom:signaturestringSee Verification Script / Central Values
Error@custom:selectorbytes4See Verification Script / Central Values
Event@custom:signaturestring(optional, e.g. @custom:topic-signature)
Event@custom:topiczerobytes32See Verification Script / Central Values
Interface@custom:interfaceidbytes4type(I).interfaceId (preferred in script) or XOR

Gold standard also uses @custom:topic-signature on some events for clarity.

Verification Script Requirement (Mandatory)

Per LR-1:

The custom NatSpec tag values (interface IDs, function selectors, and event topic0 where applicable) MUST be calculated using a dedicated Foundry Script (not one-off terminal cast commands or manual math).

  • The script must output compiler-computed values for interface IDs (using type(I).interfaceId where possible) and function selectors.
  • For events (topic0 hashes), the script should calculate or derive the values if direct compiler output for the event signature is not available in the script context.
  • This script is intended for one-time / iterative use by developers/agents to generate the exact values to paste into @custom:* tags.
  • The script (and instructions for running it) must be committed to the repository so values can be regenerated or audited at any time.

Critical Accuracy Rule: All @custom:selector, @custom:topiczero, and @custom:interfaceid values MUST be authoritatively computed using a Forge Script or Foundry Test (see scripts/foundry/ComputeNatSpecValues.s.sol). This ensures values are verifiable, reproducible in CI, and eliminates hallucination risk. Never rely solely on ad-hoc terminal cast sig / cast keccak for final values (though cast can match keccak for selectors/topics).

How to Use the Dedicated Verification Script

Primary script (committed in scripts/ per LR-1):

forge script scripts/foundry/ComputeNatSpecValues.s.sol --sig "run()" -vvv
  • The script uses type(IInterface).interfaceId for IDs and keccak256 (via Solidity) inside the compiled contract for selectors and topic0.
  • Output appears in console; copy the exact 0x... bytes into CENTRALLY_COMPUTED_NATSPEC_VALUES.md (or directly into @custom:* during population).
  • Re-run after changing any documented interface or adding symbols. Commit regenerated central values if the pass updates them.
  • For convenience during exploration, the helper scripts/compute_natspec_values.sh (updated to launch/ reference the .s.sol) can quickly emit selectors via cast, but always verify final authoritative values via the Foundry Script.

Example in script (illustrative):

// inside ComputeNatSpecValues.s.sol
console2.logBytes4(type(IFacet).interfaceId);
// and
bytes4 sel = bytes4(keccak256(bytes("facetName()")));

See scripts/foundry/ComputeNatSpecValues.s.sol (includes // tag:: and NatSpec itself) and scripts/compute_natspec_values.sh.

Existing docs/development/natspec.md (this file) reflects the full scope (incl. tests) and the required Foundry Script verification approach.

Central Values Process (Single Source of Truth)

Use docs/archive/reports/gap/CENTRALLY_COMPUTED_NATSPEC_VALUES.md exclusively (moved under docs/archive/ for GitBook hygiene; still the single source of truth for selectors/interfaceIds):

  1. Find the symbol in the relevant gap report under docs/archive/reports/gap/.
  2. Insert the @custom: lines using ONLY the pre-computed values from that file.
  3. Wrap with the exact // tag::...[] / // end::...[] as per gold standard.
  4. Verify with forge build and targeted tests.

Subagents and consumers must not independently compute values (e.g. via ad-hoc cast). Date of current central pass: 2026-07-02. Values were derived via cast for this pass but the strict requirement (LR-1) is to use the dedicated Foundry Script (scripts/foundry/ComputeNatSpecValues.s.sol) for authoritative values going forward. The central file is the single source of truth populated from the script output.

See the top of docs/archive/reports/gap/CENTRALLY_COMPUTED_NATSPEC_VALUES.md for usage and expansion instructions. Regenerate via the script when symbols are added.

Required Elements for Every Documented Symbol (LR-1)

  • Every public/external symbol in interfaces, and corresponding implementations, must be wrapped with exact include tags.
  • Interfaces must declare @custom:interfaceid (computed bytes4).
  • Events must declare @custom:topiczero (full bytes32 keccak topic hash) and preferably @custom:topic-signature.
  • Errors and functions must declare:
    • @custom:selector (exact bytes4)
    • @custom:signature (canonical string form)
  • Functions must include rich NatSpec: @notice, @param, @return, @custom:emits, @custom:throws where applicable.
  • Repos must document both the parameterized (Storage storage layoutStruct) and default overload versions (with distinct hyphenated tags).
  • Targets and Facets should use @inheritdoc where they delegate, plus their own tags for clarity.
  • Facets must fully implement IFacet with documented facetName(), facetInterfaces(), facetFuncs(), facetMetadata().

Full Scope (incl. Tests)

  • All Solidity code, including production contracts, libraries, interfaces, DFPkgs, and all test files (e.g. *.t.sol, TestBase_*.sol, Behavior_*.sol, handlers, stubs, comparators, etc.).
  • Test contracts, complex helpers, handlers, and TestBases that expose public APIs must follow the same NatSpec + include-tag standards as production code (LR-7).
  • Declaration tests for facets/packages (using Behavior_IFacet, Behavior_IDiamondFactoryPackage) reference the documented selectors/interfaceIds.

Validation

Before merging changes that affect documented symbols:

  • Confirm include tags surround the complete symbol exactly.
  • Values for custom tags match the central computed list (or freshly generated via the dedicated verification script). Do not rely solely on terminal cast.
  • Verify that facetInterfaces() and facetFuncs() (and equivalent package methods) return the documented values.
  • For test surfaces: ensure documented public test APIs are covered by Behavior validation where applicable.
  • Run forge build and relevant tests (e.g. declaration tests) to confirm.

Extraction

The include-tag convention supports extraction of exact source snippets for published documentation (GitBook, AsciiDoc) and specifications. Tag names are chosen to be stable identifiers for include:: directives in downstream systems.

Agent Use, LR-2 and LR-3 Alignment

This standard enables safe reuse by other agents and projects:

  • Follow AGENTS.md "NatSpec & Documentation Comment Standard" together with this doc (PRD LR-1 is authoritative; it requires the stricter Foundry Script + central values over older cast-only examples).
  • crane-natspec skill (and references/natspec-examples.md) operationalizes this for agents. Keep skills in sync (LR-3).
  • Supports LR-2 GitBook requirements: accurate extractable NatSpec underpins required content on CREATE3 Package for chain setup, DiamondPackageCallBackFactory public reuse (no per-chain redeploy), Registries (purpose/population/usage), ported protocol TestBases + utilities, general Sets/math/collections (cross-link to deployment, concepts, protocols).
  • LR-4 value prop: Fully documented, centrally-verified facets/packages allow "deploy once, attach everywhere" -- security via reuse of verified code (agent-error reduction), cost via not re-deploying bytecode.
  • When working: always read the target gap report + central NatSpec values + PRD LR sections + AGENTS.md + referenced sources (in that strict order) before editing.
  • Document test code when referenced in public surfaces or behaviors.

See also:

  • scripts/foundry/ComputeNatSpecValues.s.sol (PRIMARY dedicated Foundry Script per LR-1 for compiler-accurate values; see "Verification Script Requirement")
  • scripts/compute_natspec_values.sh (helper wrapper referencing the .s.sol for central pass)
  • docs/archive/reports/gap/CENTRALLY_COMPUTED_NATSPEC_VALUES.md (use ONLY these values)
  • Per-file gap reports under docs/archive/reports/gap/
  • contracts/factories/diamondPkg/Behavior_IFacet.sol and TestBase_IFacet.sol (example usage of documented IFacet surface)
  • Gold standard: contracts/access/ERC8023/* (full tags + custom + rich NatSpec + duals)

Testing Patterns

Crane tests separate infrastructure setup, behavior specification, and invariant declarations. All patterns strictly follow LR-7 Testing Standards (full/correct initialization before any asserts, exact expected-value assertions and state deltas, mandatory Behavior_* libraries for declarations, preview/execute parity, CREATE3/salt determinism + registry population verification, NatSpec + include-tags on test code that exposes APIs, handler-driven invariants, and fork parity for ports).

Production-first: Prefer real production contracts and production deploy paths (CraneTest factories, full DFPkg init). Do not invent mocks for the subject under test. See the ladder and terminology in AGENTS.md (Testing) and the crane-testing skill.

LR-2 GitBook Focus (this document): Detailed patterns, Behavior libs, handlers, TestBase usage, cross-links to registries, ported protocols, and utilities (Sets, ConstProdUtils, etc.). Content enables agents and developers to correctly exercise Crane for safe reuse of verified facets/packages (see LR-4).

Central NatSpec Rule (aligns LR-1/LR-7): Any NatSpec examples or declaration tests shown here use ONLY values from the central NatSpec values file at docs/archive/reports/gap/CENTRALLY_COMPUTED_NATSPEC_VALUES.md (archived after GitBook hygiene; still the in-repo single source for selectors). Never ad-hoc cast in docs. See NatSpec and Documentation and the dedicated scripts/foundry/ComputeNatSpecValues.s.sol verification script.

See also: repo-root AGENTS.md (crane-testing section + full TestBase/Behavior/handler examples), PRD.md (LR-2/LR-7), crane-testing skill.

Directory Layout

Test infrastructure lives in contracts/ next to production code (ensures behaviors and bases stay in sync with implementation). Concrete specifications (unit, integration, invariant, fork) live under test/foundry/spec/ mirroring the tree.

contracts/
├── test/
│   ├── CraneTest.sol                 # Factory bootstrap (create3 + diamondPackageFactory) + registries
│   ├── IHandler.sol
│   ├── behaviors/BehaviorUtils.sol
│   ├── comparators/                  # Bytes4SetComparator, AddressSetComparator, StringComparator, ...
│   ├── stubs/                        # Minimal implementations (greeter, ERC20TargetStub, ...)
│   └── ...
├── factories/diamondPkg/
│   ├── TestBase_IFacet.sol
│   └── Behavior_IFacet.sol
├── introspection/ERC165/
│   ├── TestBase_IERC165.sol
│   └── Behavior_IERC165.sol
├── access/ERC8023/
│   └── TestBase_IMultiStepOwnable.sol  # Includes MultiStepOwnableHandler
├── tokens/ERC20/
│   ├── TestBase_ERC20.sol            # Includes ERC20TargetStubHandler + invariants
│   └── ...
├── protocols/dexes/camelot/v2/
│   └── test/bases/
│       └── TestBase_CamelotV2.sol
└── protocols/.../test/bases/         # All protocol TestBases here

test/foundry/spec/
├── factories/diamondPlg/             # IFacet_Behavior_Test.sol, DiamondPackageCallBackFactory.t.sol (LR-7 decl tests)
├── protocols/dexes/camelot/v2/
│   ├── handlers/CamelotV2Handler.sol
│   └── services/...
├── tokens/ERC20/ERC20TargetStub.t.sol
└── ... (mirrors contracts/)

Key Conventions (from AGENTS.md):

  • TestBase_* and Behavior_* live in contracts/.
  • Protocol bases go in contracts/protocols/.../test/bases/.
  • Specs go in test/foundry/spec/.
  • Stubs/comparators in contracts/test/.

See AGENTS.md "Directory Structure" and "Key Testing Files".

CraneTest (inheriting BetterTest) bootstraps the two-factory system via InitDevService.initEnv. Inherit it (or a protocol base that does) for deterministic deploys + access to registries.

import {CraneTest} from "@crane/contracts/test/CraneTest.sol";

abstract contract MyTest is CraneTest {
    function setUp() public virtual override {
        CraneTest.setUp(); // if (diamondFactory == 0) { (create3Factory, diamondPackageFactory) = InitDevService... }
        // registries are now queryable on create3Factory
    }
}

Registries (see docs/deployment/create3.md "Registries Explanation" and docs/deployment/dfpkg.md): The Create3Factory system + DFPkgs automatically populate:

  • Facet Registry (IFacetRegistry)
  • Package Registry (IDiamondFactoryPackageRegistry)
  • CallTarget Registry

Consumers interact via the registry facets on the factory (no separate deploy). Per LR-7, after any factory/package deploy in tests, assert expected entries (see registry Handler_* and spec tests under test/foundry/spec/registries/).

DiamondPackageCallBackFactory reuse (LR-2): Interface ID 0x949da331. It is safe and intended for public reuse across chains/projects — do not redeploy it yourself. See docs/deployment/create3.md and IDiamondPackageCallBackFactory (selectors e.g. deploy 0xe97fac05, calcAddress 0x33a41d70, pkgOfAccount 0x8a648684 from central values).

Cross-link: contracts/factories/diamondPkg/DiamondPackageCallBackFactory.sol, InitDevService, docs/deployment/create3.md.

Protocol tests commonly inherit CraneTest directly or via TestBase_* chains that call it.

Protocol Setup TestBases

Build layered dependencies via inheritance. Each calls super.setUp() (or explicit parent) and deploys only if not preset (address(x) == address(0) guard).

Mermaid example (Camelot):

flowchart TB
    CraneTest["CraneTest<br/>(factories + core + registries)"]
    CraneTest --> Weth["TestBase_Weth9"]
    Weth --> Camelot["TestBase_CamelotV2"]
    Camelot --> Pools["TestBase_CamelotV2_Pools"]
    Pools --> Your["YourIntegrationTest"]

    classDef base fill:#2c3e50,stroke:#80cbc4,color:#ecf0f1
    class CraneTest,Weth,Camelot,Pools base

Camelot example (contracts/protocols/dexes/camelot/v2/test/bases/TestBase_CamelotV2.sol):

abstract contract TestBase_CamelotV2 is TestBase_Weth9 {
    ICamelotFactory internal camelotV2Factory;
    ICamelotV2Router internal camelotV2Router;

    function setUp() public virtual override {
        camelotV2FeeToSetter = makeAddr("...");
        TestBase_Weth9.setUp();
        if (address(camelotV2Factory) == address(0)) {
            camelotV2Factory = new CamelotFactory(camelotV2FeeToSetter);
        }
        if (address(camelotV2Router) == address(0)) {
            camelotV2Router = new CamelotRouter(address(camelotV2Factory), address(weth));
        }
    }
}

Cross-links for detailed protocol TestBase + usage (LR-2 required areas):

  • All DEXes: docs/protocols/dexes.md (Camelot V2 + TestBase_CamelotV2 + TestBase_CamelotV2_Pools + CamelotV2Handler; Uniswap V2/V3/V4 + Aerodrome + Slipstream bases + fork variants; Balancer V3 TestBase_BalancerV3Vault + real BalancerV3VaultDFPkg + pool DFPkg bases).
  • Lending: docs/protocols/lending.md (Aave ProtocolV3TestBase + Aave v4 Base + AaveV4TestOrchestration + deployTestEnv; Euler EVC/EVault test bases; combinable with CraneTest).
  • Protocol ports vs forks: Protocol ports under contracts/protocols/.../stubs/ are real/protocol-faithful implementations for fast hermetic deploy inside TestBases (not canned interface mocks). Fork bases (e.g. TestBase_*Fork) use vm.createSelectFork + network constants. Do not mix hermetic ports and fork addresses in one base without a clear mode switch.
  • Harness stubs vs mocks: contracts/test/stubs/ and mintable ERC20s add test controllability outside the SUT. Do not mock facets/DFPkgs/diamonds under test.
  • DFPkg integration tests (Balancer example): use diamondFactory.deploy(pkg, pkgArgs) with real facets (never address(0) — LR-7 violation).

See also contracts/protocols/.../test/bases/ and test/foundry/spec/... for concrete inheritance + usage.

Behavior TestBases + Behavior Libraries (Mandatory for Standards per LR-7)

Behavior TestBases declare expected values via virtuals; concrete tests supply the SUT instance and run the assertions.

Core example (contracts/factories/diamondPkg/TestBase_IFacet.sol):

abstract contract TestBase_IFacet is Test {
    IFacet internal testFacet;

    function setUp() public virtual { testFacet = facetTestInstance(); }

    function facetTestInstance() public virtual returns (IFacet);
    function controlFacetName() public view virtual returns (string memory);
    function controlFacetInterfaces() public view virtual returns (bytes4[] memory);
    function controlFacetFuncs() public view virtual returns (bytes4[] memory);

    function test_IFacet_facetName() public view { ... Behavior ... }
    function test_IFacet_FacetInterfaces() public { ... length + Behavior.areValid_ ... }
    function test_IFacet_FacetFunctions() public { ... }
    function test_IFacet_FacetMetadata_Consistency() public { ... }
    function test_IFacet_InterfaceId_Computation() public pure { ... }
}

Behavior Libraries (Behavior_IInterface) encapsulate validation + structured logging. Never duplicate assertions for IFacet / IDiamondFactoryPackage / protocol standards.

Patterns:

  • expect_* — store expectations (e.g. expect_IFacet_facetInterfaces(subject, expected))
  • areValid_* / isValid_* — direct compare (returns bool, logs on mismatch)
  • hasValid_* — validate against prior expect_* (used in declaration tests)

See full in Behavior_IFacet, Behavior_IERC165, BehaviorUtils.

IFacet Declaration Tests (LR-7 mandatory): Every Facet must declare correct facetInterfaces() / facetFuncs() / facetName() / facetMetadata(). Use Behavior_IFacet (or TestBase_IFacet).

Use ONLY central values from CENTRALLY_COMPUTED_NATSPEC_VALUES.md:

  • facetName(): 0x5b6f4d01
  • facetInterfaces(): 0x2ea80826
  • facetFuncs(): 0x574a4cff
  • facetMetadata(): 0xf10d7a75
  • supportsInterface(bytes4): 0x01ffc9a7

Example in tests (see test/foundry/spec/factories/diamondPlg/IFacet_Behavior_Test.sol and Balancer *Facet_IFacet.t.sol):

// controlFacetInterfaces / controlFacetFuncs return the expected arrays
assertTrue(Behavior_IFacet.areValid_IFacet_facetInterfaces(testFacet, control..., actual));

Package Declaration Tests (LR-7): packageName() (0xabc8b346), facetCuts() (0xa4b3ad35), diamondConfig() (0x65d375b3), calcSalt(bytes) (0xd82be56e), initAccount(bytes) (0x870d4838), postDeploy(address) (0x70068fcf), facetInterfaces() etc. Full lifecycle (including delegatecall initAccount) + salt determinism + real-facet DFPkg tests.

See DiamondPackageCallBackFactory.t.sol for LR-7 examples using Behavior + exact asserts.

Handlers for Invariant / Fuzz Testing

Handlers expose fuzzer-callable ops, normalize inputs, declare expectations with vm.expect*, track ghost state for invariants.

Actor/seed normalization (small fixed address space):

function addrFromSeed(uint256 seed) public pure returns (address) {
    return address(uint160((seed % 16) + 1));
}

useActor modifier example (MultiStepOwnableHandler):

modifier useActor(uint256 actorIndexSeed) {
    currentActor = actors[BetterVM.bound(actorIndexSeed, 0, actors.length-1)];
    vm.startPrank(currentActor); _; vm.stopPrank();
}

Explicit expectations (ERC20TargetStubHandler transfer):

vm.prank(owner);
if (amount > bal) {
    vm.expectRevert( abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, ...) );
    sut.transfer(to, amount); return;
}
vm.expectEmit(true, true, false, true);
emit IERC20Events.Transfer(owner, to, amount);
sut.transfer(to, amount);

Invariant registration (in TestBase setUp):

targetContract(address(handler));
targetSelector(FuzzSelector({addr: address(handler), selectors: [handler.transfer.selector, ...]}));

Then:

function invariant_totalSupply_equals_sumBalances() public view { ... exact sum assert ... }

Examples:

  • ERC20: contracts/tokens/ERC20/TestBase_ERC20.sol (handler tracks _expectedAllowance, seen addrs, asserts deltas inside handler + invariants in base)
  • MultiStep: contracts/access/ERC8023/TestBase_IMultiStepOwnable.sol (ghostCurrentOwner, access matrix negative paths)
  • Camelot K invariants: test/foundry/spec/protocols/dexes/camelot/v2/handlers/CamelotV2Handler.sol (kBefore/kAfter, proportional burn checks, op counters)

See AGENTS.md "Declarative Invariant Testing Pattern" and contracts/test/IHandler.sol.

Per LR-7: every state change uses expectEmit + exact post-state asserts (handler or invariant).

Comparator Infrastructure

Comparators store expected collections keyed by (subject address, selector). Provide rich error diffs + console.logBehavior*.

Used by Behavior libs (e.g. Bytes4SetComparatorRepo._recExpectedBytes4).

See contracts/test/comparators/*.sol and usage in Behavior_IFacet.

Protocol-specific:

  • DEX services + ConstProdUtils: Camelot/Aerodrome/Uniswap V2 use ConstProdUtils._saleQuote / _purchaseQuote etc for parity checks against live router/pool execution inside TestBases. See docs/protocols/dexes.md, contracts/utils/math/ConstProdUtils.sol, dedicated constProdUtils spec tests, and CamelotV2Service.
  • Lending utils: WadRayMath, SpokeUtils, LiquidationLogic (Aave); EVC Set/Transient/Lens/IRM (Euler).

General utilities & type libs (LR-2 required coverage):

  • Sets: AddressSet, Bytes32Set, Bytes4Set (and *Repo) for actor tracking, expected interface lists, allowance keys in handlers/comparators. Dual _layoutStruct(slot) + default overload pattern.
  • Other: BetterEfficientHashLib, UInt256, BetterAddress, rate provider adapters, Permit2Aware, etc.

Usage in tests: handlers/comparators use Sets/Repos for ghost state; dex TestBases use ConstProdUtils for expected quotes.

See contracts/utils/collections/sets/, contracts/utils/math/, protocol services/, AGENTS.md, and docs/protocols/*.

  1. Inherit or extend appropriate TestBase_* (call parent setUp() first).
  2. For interface compliance/declaration: implement control virtuals + inherit TestBase_IFacet (or call Behavior_IFacet directly). Assert length + areValid_*.
  3. For stateful/invariants: implement Handler (normalize, expect*, track ghosts), register in setUp, write invariant_* exact asserts.
  4. Use Behavior libs for standards; comparators for collections.
  5. Full init (real facets), exact deltas, NatSpec on public test surface (with central values + // tag::Name[]).
  6. Verify: forge test --match-path ... -vvv; assert registry entries + determinism for factories.

LR-7 enforcement examples in Crane:

  • Balancer DFPkg real-facet integration tests (no 0 addresses).
  • Camelot/Aerodrome quote parity (preview == execute deltas).
  • IFacet declaration tests using Behavior + central selectors.
  • Handler K invariants + burn proportionality.
  • Registry population + salt determinism asserts post-deploy.

NatSpec + Include-Tags on Test Artifacts (LR-1/LR-7)

Handlers, TestBases, Behavior helpers that expose public APIs must carry full NatSpec + exact // tag::Symbol(params)[] / // end:: (hyphenated for overloads) + @custom:selector/@custom:signature using central values.

Gold standard examples live in contracts/access/ERC8023/* and updated factory tests.

See docs/development/natspec.md.

Cross-Reference Summary (LR-2 GitBook Navigation)

This surface makes Crane's validated components reusable with high confidence: initialize once via factories/TestBases, attach DFPkgs, rely on Behavior-validated declarations.

Key Files (see AGENTS.md for complete list)

  • /contracts/test/CraneTest.sol
  • /contracts/factories/diamondPkg/{TestBase_IFacet.sol,Behavior_IFacet.sol}
  • /contracts/tokens/ERC20/TestBase_ERC20.sol
  • /contracts/access/ERC8023/TestBase_IMultiStepOwnable.sol
  • /contracts/protocols/dexes/camelot/v2/test/bases/TestBase_CamelotV2.sol
  • test/foundry/spec/factories/diamondPlg/*_Behavior_Test.sol (LR-7 exemplars)
  • contracts/utils/collections/sets/*SetRepo.sol (and comparators)
  • contracts/utils/math/ConstProdUtils.sol

Run: forge test, targeted with --match-path, or invariants automatically via forge test.

(End of detailed LR-2/LR-7 aligned testing patterns.)

Multi-Step Ownable (ERC8023)

Two-step ownership transfer with a configurable confirmation delay.

Pattern

  1. Current owner calls transferOwnership(newOwner).
  2. A pending owner is recorded together with a timestamp.
  3. After the delay elapses, the pending owner calls acceptOwnership().

The delay prevents accidental or malicious immediate takeover.

Implementation Layers

  • MultiStepOwnableRepo — stores pending owner and timestamp; contains the guards and state transitions.
  • MultiStepOwnableTarget — implements the external interface.
  • MultiStepOwnableFacet — exposes the functions through the Diamond.

A corresponding DFPkg exists for inclusion in new proxies.

Storage Slot

crane.access.erc8023

Most packages include the multi-step ownable facet as a base building block alongside operable and introspection facets.

Operable

Granular operator permissions with support for global and per-function operators.

Storage

crane.access.operable

Layers

  • OperableRepo — mappings for operators and function-specific operators; guard _onlyOperator.
  • OperableTarget
  • OperableFacet
  • OperableModifiers

Usage

Contracts inherit OperableModifiers to obtain the onlyOperator modifier.

Inside Repos and Targets, call OperableRepo._onlyOperator() directly for internal enforcement.

Function Operators

An address can be granted operator rights for a specific selector via setFunctionOperator. The guard checks both global isOperator and the per-selector mapping before reverting.

This enables least-privilege operator roles without deploying separate role contracts for every function.

ERC20

Crane provides a complete ERC20 implementation (including metadata, permit, and supply controls) as a Diamond Factory Package.

Package

ERC20DFPkg

Constructor receives the pre-deployed ERC20Facet.

Deployment arguments (PkgArgs):

  • name, symbol, decimals
  • totalSupply
  • recipient (initial holder)
  • optionalSalt (for address customization)

The package installs the ERC20 facet and executes initialization that writes name/symbol/decimals and mints the initial supply to the recipient.

Storage

  • eip.erc.20 for balances, allowances, and metadata.
  • eip.erc.2612 for nonces when the permit facet is installed.

Reuse

A single ERC20Facet deployment is referenced by every ERC20 proxy created from ERC20DFPkg. The facet contains the logic for transfer, approve, permit, etc. Each proxy holds only its own balance and allowance mappings under its isolated storage slot.

Extensions

  • ERC20PermitDFPkg adds EIP-2612 permit support.
  • ERC4626*DFPkg variants provide tokenized vault implementations on the same pattern.

All token packages follow the same DFPkg lifecycle: facets are deployed once; packages produce many proxies at deterministic addresses.

Protocol & core maturity status

Honest maturity labels for public consumers. Core factories, access, tokens, and registries are the primary supported product surface. Protocol trees vary widely.

LabelMeaning
stableCrane-native patterns in active use; TestBase/Behavior coverage expected for public APIs
experimentalUsable for integration/learning; APIs or ports may change; not a production guarantee
vendoredUpstream sources under contracts/external or protocol trees; fidelity to upstream, Crane wrappers partial
WIPIncomplete port or scaffolding; do not rely on for mainnet

Core framework

AreaPathMaturity
CREATE3 factory + DFPkgcontracts/factories/stable
Diamond package factorycontracts/factories/diamondPkg/stable
Access (Operable, ERC8023 MultiStepOwnable, reentrancy)contracts/access/stable
Tokens (ERC20/2612/4626 + DFPkgs)contracts/tokens/stable
Registriescontracts/registries/stable
Introspection (ERC165/2535 helpers)contracts/introspection/stable
Utils (math, sets, crypto)contracts/utils/stable (some TODOs remain)
InitDev / InitBc servicescontracts/Init*.solstable
Bounties DFPkgcontracts/bounties/experimental (product-adjacent)

DEX / AMM ports

ProtocolMaturityNotes
Uniswap V2/V3/V4 services & wrappersexperimentalvendoredSkills + partial Crane services; verify TestBases per path
Balancer V3experimentalvendoredVault/pool integration skills; port depth varies
Aerodrome + SlipstreamexperimentalvendoredBase-focused; gauge/CL surfaces
CamelotexperimentalService wrappers present

Lending / CDP / other

ProtocolMaturityNotes
Aave V3vendored / experimental wrappersLarge vendor tree
Aave V4 Hub/SpokeWIPexperimentalPort in progress; not production-complete
Euler EVC/EVKvendored / experimental
Compound Cometvendored / experimentalSkills available
Resupplyexperimental
Reliquaryexperimental
Pendle / Frax / Liquity / Skyvendored / WIPLarge trees; use status carefully
Reactive Network demosexperimentalMessaging demos

CI vs full monorepo

GitHub Actions uses FOUNDRY_PROFILE=ci, which skips contracts/external/**, contracts/protocols/**, and heavy fork/protocol tests to avoid OOM. A green CI run proves framework core, not every protocol port.

See CONTRIBUTING.md and foundry.toml [profile.ci].

DEX Protocol Integrations

Crane provides deep, agent-ready integrations for major DEXes using the same Facet-Target-Repo (FTR) + Service + DFPkg patterns as the core framework. Integrations emphasize:

  • Shared constant-product math via ConstProdUtils (for V2-style and volatile pools across Camelot, Uniswap V2, Aerodrome V1).
  • Protocol-specific AwareRepo libraries for dependency injection (routers, factories, vaults).
  • Stateless *Service libraries for complex operations (swaps, deposits, quotes) using structs to avoid stack-too-deep.
  • Stubs and TestBases for isolated unit tests; separate fork bases for mainnet parity.
  • Where DEX components are themselves upgradeable (primarily Balancer V3), full use of Crane's DFPkg + DiamondPackageCallBackFactory infrastructure.

See the protocol-specific skills for deeper agent guidance:

  • crane-balancer + balancer-v3-* (deepest port: full Vault as a Diamond, multiple pool types as DFPkgs/facets, router Diamond, hooks, rate providers, buffer support).
  • crane-uniswap + uniswap-v*-* (V2 simple AMM, V3 concentrated, full V4 PoolManager/PositionManager/hooks/Quoter with flash accounting and hooks).
  • crane-aerodrome + slipstream-* + aerodrome-* (volatile/stable + concentrated liquidity (Slipstream), gauges, voter, ve-tokenomics, rewards, bribes).
  • crane-camelot.

Cross-reference:

Protocol Directory Structure

Each DEX lives under contracts/protocols/dexes/{protocol}/{version}/:

protocols/dexes/{protocol}/{version}/
├── *AwareRepo.sol          # DI for router/factory/vault (e.g. CamelotV2RouterAwareRepo)
├── services/               # Business logic (CamelotV2Service, AerodromeServiceVolatile, UniswapV2Service)
├── stubs/                  # Local mock implementations of protocol contracts (for unit tests)
├── interfaces/             # (some protocols)
└── test/
    └── bases/
        └── TestBase_*.sol  # Shared setup (unit + separate *Fork for mainnet)

Stubs and TestBases are test infrastructure only (live in contracts/ alongside prod code per Crane conventions). Actual specs live in test/foundry/spec/protocols/dexes/....

All follow deterministic deployment where applicable (CREATE3 for facets/packages when using DFPkgs; direct deployment for stubs in tests).

Shared Protocol-Specific Utilities: ConstProdUtils

ConstProdUtils (in contracts/utils/math/ConstProdUtils.sol) is the foundational math library for constant-product (xy=k) AMMs and is used by Camelot V2, Uniswap V2, Aerodrome volatile pools, and related services.

Key capabilities (used for quoting without side effects, plus deposit/withdraw calcs):

  • _sortReserves(...) (overloads for fees too)
  • _depositQuote(...), _withdrawQuote(...)
  • _saleQuote(uint256 amountIn, uint256 reserveIn, uint256 reserveOut, uint256 feePercent)
  • _purchaseQuote(...) (exact in for desired out)
  • _swapDepositSaleAmt(...) (optimal swap amount before addLiquidity for single-sided)
  • _equivLiquidity(...)

Usage in services (see AGENTS.md example):

// In CamelotV2Service / UniswapV2Service / AerodromeServiceVolatile
using ConstProdUtils for uint256;

uint256 expectedOut = ConstProdUtils._saleQuote(amountIn, reserveIn, reserveOut, fee);

See dedicated tests exercising parity between quotes and live execution:

  • test/foundry/spec/utils/math/constProdUtils/ConstProdUtils_purchaseQuote_Camelot.t.sol
  • test/foundry/spec/utils/math/constProdUtils/ConstProdUtils_calculateFeePortionForPosition_Aerodrome.t.sol
  • test/foundry/spec/utils/math/constProdUtils/ConstProdUtils_priceImpact.t.sol

TestBases for these inherit protocol setup (see below) then create real pairs/pools via stubs + services and assert ConstProdUtils results == actual router/pool outputs and state deltas.

Camelot V2

Location: contracts/protocols/dexes/camelot/v2/

  • CamelotV2FactoryAwareRepo.sol, CamelotV2RouterAwareRepo.sol (slot: crane.camelot.v2.router.aware)
  • services/CamelotV2Service.sol_deposit, _withdrawDirect, _swap (and overloads), _prepareSwap, balance/sale helpers. Uses ConstProdUtils, referrer support, fee-on-transfer handling.
  • stubs/: CamelotFactory, CamelotRouter, CamelotPair (plus UniswapV2ERC20 + libs).

Test usage:

  • Inherit TestBase_CamelotV2 (which inherits TestBase_Weth9).
  • Calls TestBase_Weth9.setUp() then deploys CamelotFactory(feeToSetter) + CamelotRouter(factory, weth) if not pre-set.
  • Provides: camelotV2Factory, camelotV2Router, weth.

Example inheritance chain (unit tests):

CraneTest (optional, for diamond consumers)
  └── TestBase_Weth9
        └── TestBase_CamelotV2
              └── TestBase_ConstProdUtils_Camelot (or your test)

Specialized:

  • TestBase_ConstProdUtils_Camelot creates balanced/unbalanced/extreme pools + tokens using ERC20PermitMintableStub, then exercises service vs. ConstProdUtils.
  • Direct specs: test/foundry/spec/protocols/dexes/camelot/v2/services/CamelotV2Service.t.sol, invariant tests, fee variants, multihop, referrer, stableSwap, asymmetric fees.
  • Handler for fuzz: handlers/CamelotV2Handler.sol (tracks expected K for invariant_* checks on swaps/mints/burns).

Usage pattern in tests:

import {TestBase_CamelotV2} from "@crane/contracts/protocols/dexes/camelot/v2/test/bases/TestBase_CamelotV2.sol";
import {CamelotV2Service} from "@crane/contracts/protocols/dexes/camelot/v2/services/CamelotV2Service.sol";

contract MyCamelotTest is TestBase_CamelotV2 {
    function setUp() public virtual override {
        TestBase_CamelotV2.setUp();
        // mint/deal tokens, then CamelotV2Service._deposit(...) or router calls
    }
}

Stubs are used automatically by the TestBase. For fork tests, use separate fork bases + network constants (e.g. from contracts/constants/networks/).

Uniswap V2

Location: contracts/protocols/dexes/uniswap/v2/

  • aware/: UniswapV2FactoryAwareRepo.sol, UniswapV2RouterAwareRepo.sol
  • services/UniswapV2Service.sol — swap/quote helpers using ConstProdUtils._saleQuote, deposit/sale helpers.
  • libraries/TransferHelper.sol; stubs/: UniV2Factory, UniV2Router02, UniV2Pair + deps.

Test usage:

  • TestBase_UniswapV2 (inherits TestBase_Weth9) deploys fee setter + stubs for factory/router.
  • Provides addBalancedUniswapLiquidity(...) helper (uses ConstProdUtils._equivLiquidity + sortedReserves + deal/approve).
  • TestBase_UniswapV2_Pools extends for pool creation helpers.

Example test base usage mirrors Camelot. Specs live under test/foundry/spec/protocols/dexes/uniswap/v2/services/... and aware/.... Fork support via test/foundry/fork/ethereum_main/uniswapV2/.

Uniswap V3 + V4

Uniswap V3:

  • Full core (UniswapV3Factory, UniswapV3Pool, UniswapV3PoolDeployer) + extensive periphery (SwapRouter, NonfungiblePositionManager, etc.) + libraries.
  • test/bases/TestBase_UniswapV3 (inherits TestBase_Weth9 + callbacks) provides factory + fee/tick constants.
  • Periphery TestBase: TestBase_UniswapV3Periphery.
  • Tests: tick/swap math, periphery descriptor, etc. Fork tests separate.

Uniswap V4 (deepest):

  • Complete PoolManager, PositionManager, V4Router, Quoter, hooks base + examples (WETHHook, etc.), ERC6909 claims, Permit2 integration, EIP712, multicall.
  • Many public hook examples and aggregator patterns in hooks/public/.
  • Uses extensive shared libs under uniswap/libraries/.
  • Tests include heavy fuzz/fork for hooks + full e2e (many under test/foundry/spec/protocols/dexes/uniswap/v4/... and aggregator subdirs).
  • Lifecycle details in dedicated doc (cross-link above).

Shared math (used by V3 + Slipstream + others): TickMath, FullMath, SqrtPriceMath, SwapMath, LiquidityMath, FixedPoint96, etc.

Aerodrome V1 + Slipstream

Aerodrome V1 (volatile + stable + full governance):

  • aware/: AerodromeRouterAwareRepo.sol, AerodromePoolMetadataRepo.sol
  • services/: AerodromeService.sol, AerodromeServiceVolatile.sol, AerodromeServiceStable.sol (use ConstProdUtils for volatile; separate stable math).
  • Full stubs for: Pool, Router, Voter, VotingEscrow, Gauge(s), Minter, Rewards (various), Factories (pool/gauge/voting/managed), AirdropDistributor, governors, etc. + libs (SafeCastLibrary).
  • TestBase_Aerodrome (inherits TestBase_Weth9): deploys the entire protocol stack (AERO token first, factories, voter, distributor, minter, router, art proxy, esrow, gauges, rewards, governors). Sets labels.
  • TestBase_Aerodrome_Pools extends for balanced/unbalanced/stable test pools + tokens (similar structure to Camelot ConstProd base).

Specs: test/foundry/spec/protocols/dexes/aerodrome/v1/services/*, aware tests. Fork tests: test/foundry/fork/base_main/aerodrome/ using TestBase_AerodromeFork.

Slipstream (Concentrated Liquidity):

  • CL implementation (CLFactory, CLPool, Position/Tick libs) + fee modules (custom swap/unstaked) + callbacks.
  • Reward utils: SlipstreamRewardUtils.sol.
  • TestBase_Slipstream (abstract, uses mock CLPool due to solc version): constants for FEE_LOW/MED/HIGH + TICK_SPACING; imports Uniswap V3 math libs.
  • TestBase_SlipstreamFork for live Base mainnet.
  • Usage/tests: reward utils, gas, swap utils under fork + spec. Shares math with Uniswap V3.

See Aerodrome README.md in source for port notes + ConstProdUtils integration.

Balancer V3 (Diamond-Native Port)

The most advanced port — Balancer V3 Vault, pools, and router are built with Crane's own Diamond/DFPkg machinery:

  • vault/diamond/: Full Vault facets (swap, liquidity, transient accounting, auth, pool, etc.) + DFPkg (BalancerV3VaultDFPkg).
  • Pool types as DFPkgs/facets + targets/repos (weighted, stable, constant-product, gyro 2CLP/ECLP, LBP, ReClamm, cow pools).
  • Rate providers as facets/DFPkg + factory service (ERC4626RateProviderFacetDFPkg).
  • Router as its own Diamond + DFPkg.
  • Hooks examples (BaseHooksTarget, StableSurgeHook, MevCaptureHook, etc.) + buffer/composite routers.
  • Utils: TokenConfigUtils, weighted math, BalancerV3WeightedPoolQuote, etc.
  • Aware: BalancerV3VaultAwareRepo.sol (slot example in AGENTS.md: "protocols.dexes.balancer.v3.vault.aware").

Test usage (critical LR-2 / LR-7 area):

  • TestBase_BalancerV3BaseTest (ported minimal Balancer test utils for tokens, timestamps, helpers).
  • TestBase_BalancerV3Vault (uses CraneTest indirectly via deployers + mocks, deploys real BalancerV3VaultDFPkg in some paths, RouterMock/Buffer/etc., VaultContractsDeployer).
  • Specialized: TestBase_BalancerV3_WeightedPool, TestBase_BalancerV3_8020WeightedPool, router base.
  • Mocks (dozens): ERC20TestToken, PoolHooksMock, RateProviderMock, RouterMock, ArrayHelpers, etc. live in test/mocks/.
  • Utils/Deployers: VaultContractsDeployer, pool-specific deployers.
  • Declaration tests for facets (following IFacet + Behavior patterns):
    • Many ...Facet_IFacet.t.sol (e.g. BalancerV3WeightedPoolFacet_IFacet.t.sol, BalancerV3StablePoolFacet_IFacet.t.sol, gyro, constprod, LBP, etc.).
    • Use direct asserts or core Behavior_IFacet.areValid_IFacet_facetInterfaces(...) + TestBase_IFacet patterns (see contracts/factories/diamondPkg/{TestBase_IFacet,Behavior_IFacet}.sol and test/foundry/spec/factories/diamondPlg/IFacet_Behavior_Test.sol).
    • Verify facetName() (selector 0x5b6f4d01), facetInterfaces() (0x2ea80826), facetFuncs() (0x574a4cff), facetMetadata() (0xf10d7a75), plus protocol interfaces.
  • DFPkg tests: ...DFPkg.t.sol, ...DFPkg_Integration.t.sol, ...DFPkg_RealFacets.t.sol (full init with real facets, no address(0)).
  • E2E + invariants: rounding, reClamm, hooks, swap/liquidity flows.
  • Vault integration example: BalancerV3RouterVaultIntegration.t.sol (deploys real BalancerV3VaultDFPkg via new + pkg init using IBalancerV3VaultDFPkg.PkgInit).

Inheritance example for vault/pool tests:

Test (or CraneTest)
  └── TestBase_BalancerV3
        └── TestBase_BalancerV3Vault
              └── TestBase_BalancerV3_WeightedPool (or 8020)

See also test/foundry/spec/protocols/dexes/balancer/v3/vault/diamond/BalancerV3VaultDFPkg.t.sol and pool DFPkg specs. Use InitDevService / CraneTest factories when full deterministic DFPkg proxy deployment is needed.

Using DEX Integrations in Tests (Summary + Best Practices)

  1. Choose the right base:

    • Unit/isolated: TestBase_CamelotV2, TestBase_UniswapV2, TestBase_Aerodrome, TestBase_UniswapV3, TestBase_Slipstream, Balancer *Vault/pool bases.
    • Always call Super.setUp() first.
    • Fork: TestBase_*Fork variants (use with vm.createSelectFork + network consts).
  2. Stubs vs real:

    • Stubs (in */stubs/) for fast, hermetic tests. Deployed inside TestBase if address(xxx) == address(0).
    • Never use stubs on fork.
  3. Behavior / declaration testing for facetized DEX parts:

    • Balancer pools/vault/router facets declare IFacet surface.
    • Prefer Behavior_IFacet helpers + expect_* / hasValid_* (or direct parity tests in *IFacet.t.sol).
    • Full initialization required (real facet addresses passed to DFPkgs, never 0).
  4. Services + quoting:

    • Use *Service._xxx(...) for expected behavior in tests (compare to direct router + ConstProdUtils).
    • Example from Camelot tests: compute via _saleQuote then execute and assert equality + events/balances.
  5. Invariant / handler testing:

    • Camelot example: CamelotV2Handler + targetContract + invariant_* (K tracking).
  6. Consumers using AwareRepos + DFPkgs:

    • In your Diamond consumer: CamelotV2RouterAwareRepo._initialize(routerFromTestBase);
    • Deploy consumer via diamondPackageFactory from CraneTest.
    • For Balancer: pass vault-aware facets into pool DFPkg PkgInit.
  7. Cross-protocol:

    • All V2/volatile use ConstProdUtils.
    • CL (UniswapV3/Slipstream) share tick/sqrt math.
    • Balancer has its own fixed-point + scaling (ported).

See concrete examples in:

  • test/foundry/spec/protocols/dexes/camelot/v2/
  • test/foundry/spec/protocols/dexes/aerodrome/...
  • test/foundry/spec/protocols/dexes/balancer/v3/... (esp. DFPkg + IFacet + integration)
  • test/foundry/spec/protocols/dexes/uniswap/...
  • ConstProdUtils tests (inherit protocol TestBases)
  • Full Crane test inheritance: AGENTS.md "TestBase Inheritance Chain Example".
  • DFPkg + factory flow for DEX pool wrappers: Diamond Factory Packages, IDiamondFactoryPackage (central selectors: facetCuts() 0xa4b3ad35, initAccount() 0x870d4838, postDeploy() 0x70068fcf).
  • General utilities (Sets, other math): Utilities Overview; see also contracts/utils/collections/, contracts/utils/math/.
  • Registries + CREATE3: Registries, CREATE3.
  • Agent skills: AI Agent Skills.

Consult individual skills and the source contracts/protocols/dexes/*/README.md (where present) for integration recipes. All tests follow LR-7 rules (full init before asserts, exact vs side-effect, Behavior where applicable).

This surface enables safe, reusable DEX logic inside upgradeable Diamonds with minimal redeployment cost.

See also

Lending Protocol Integrations

Crane ports major lending protocols with full fidelity (faithful source mirrors) alongside Crane-native patterns for reuse.

  • Aave v3.6 and Aave v4 (Hub/Spoke + TokenizationSpoke + PositionManager + gateways + dynamic config + risk).
  • Euler (EVC batching, modular EVault, rich periphery, sophisticated oracles).
  • Morpho (Blue isolated markets + MetaMorpho V1.1 + Public Allocator + Vault V2 + Bundler3; Crane Service/TestBase/fork parity under protocols/lending/morpho/).

See dedicated skills: aave-, euler-, morpho-architecture, morpho-blue-operations, morpho-vaults, crane-morpho (and subskills like aave-v3-pool, aave-v3-stata-token, euler-evc, euler-evk-*).

Native Crane pieces include Permit2Aware (IPermit2Aware + Repo/Target) and rate provider patterns (IERC4626RateProvider) usable across lending and yield.

Ports are structured for reuse via *AwareRepo + *Service patterns (where Crane wrappers added) and direct integration with core (DFPkgs, registries, factories).

See Codebase Map, Testing Patterns, lifecycle notes under protocols/lending/, and internal port history under docs/archive/internal-plans/ (not part of primary GitBook nav).

Aave Ports

Aave v3.6

Full upstream port of Aave V3.6 (Pool, PoolConfigurator, AToken/VariableDebtToken, AaveOracle, PriceOracleSentinel, incentives/rewards, stata-token extensions (ERC4626), v3-config-engine, helpers like ProtocolDataProvider/WrappedTokenGatewayV3, and supporting libraries).

Key directories:

  • contracts/protocols/lending/aave/v3.6/protocol/ — core pool logic, tokenization, configuration, libraries (logic + math).
  • contracts/protocols/lending/aave/v3.6/misc/, helpers/, extensions/, rewards/, treasury/.
  • Deployments use procedures under deployments/procedures/.

Aave v4 (Hub/Spoke)

Deep port of Aave V4 architecture:

  • Hub (Hub.sol, HubConfigurator, AssetInterestRateStrategy, HubStorage): central liquidity and accounting.
  • Spoke (Spoke.sol, SpokeConfigurator, AaveOracle, TokenizationSpoke (ERC4626), TreasurySpoke): risk, positions, tokenization.
  • PositionManager layer (PositionManagerBase, ConfigPositionManager, Giver/TakerPositionManager, SignatureGateway, NativeTokenGateway): intent-based and EIP-712 flows.
  • Config engine, deployments/orchestration (batches + procedures), extensive math (WadRayMath, MathUtils, PercentageMath, SharesMath) and spoke utils (SpokeUtils, LiquidationLogic, UserPositionUtils, etc.).

Key files:

  • contracts/protocols/lending/aave/v4/hub/Hub.sol
  • contracts/protocols/lending/aave/v4/spoke/TokenizationSpoke.sol
  • contracts/protocols/lending/aave/v4/position-manager/*
  • Deployment orchestration: AaveV4TestOrchestration / procedures (see deployments/).

Vendor provenance and port details: docs/protocols/lending/aave/v4/VENDOR_PROVENANCE.md.

Both Aave versions emphasize exact fidelity; upstream interfaces preserved (imports remapped to @crane/).

Euler Port (v1)

Full EVC + EVK + periphery port (see docs/protocols/lending/euler/v1/ and dedicated lifecycle docs):

  • EVC (evc/EthereumVaultConnector.sol, TransientStorage.sol, Set.sol, ExecutionContext.sol): batching, deferred checks, onBehalfOf, controllers. Core of all authenticated flows.
  • EVault (vault/EVault/): modular via Dispatch + modules (Vault, Borrowing, Liquidation, RiskManager, Governance, Token, Initialize). Uses initOperation, cache, LTV, liquidity utils, hooks.
  • Periphery: Lens (AccountLens, VaultLens, OracleLens, IRMLens), IRM factories (adaptive, kink, etc.), Perspectives (for validation/whitelisting), Swaps handlers, Governor patterns, ERC4626EVC wrappers/collateral variants, PublicAllocator (for EulerEarn).
  • Oracle: EulerRouter + rich adapters (chainlink, pyth, redstone, rate, uniswap, fixed, pendle, lido, chronicle, etc. incl. RateProviderOracle).
  • EulerEarn / EulerSwap: allocator vaults and concentrated swap surfaces backed by EVaults.

Lifecycle emphasis (from EulerV1_Lifecycle.md): custody/accounting in EVault/Earn/Swap; auth + deferred health in EVC. callThroughEVC, initOperation boundary.

Wrapper value design note: EulerV1_Wrapper_Value_Design.md.

How to Integrate and Use

  • Direct: import interfaces from contracts/protocols/lending/{aave,euler}/.../interfaces/ and call (e.g. IPool.supply, IEVault.deposit, IEthereumVaultConnector.batch).
  • Crane-structured: Use native Permit2AwareRepo / Permit2AwareTarget for gasless approvals (see tokens/ERC4626/* and l2s relayers for examples). Rate providers via IERC4626RateProvider for yield-bearing assets in lending contexts.
  • With DFPkgs / Diamonds (for your own faceted layers): attach custom facets that use the ported lending primitives via injection. Registries help resolve shared facets (see central IDiamondFactoryPackage selectors e.g. facetCuts() : 0xa4b3ad35).
  • Oracles/risk: EulerRouter or AaveOracle plugged via adapters; combine with Crane oracles.

See AGENTS.md: "*AwareRepo for dependency injection", "*Service for business logic", and DFPkg pattern for composing.

Test Usage (TestBases, Stubs, Handlers, Invariants)

Lending ports include comprehensive test suites directly under test/foundry/spec/protocols/lending/ (vendored + extensions). They are executable via:

forge test --match-path "test/foundry/spec/protocols/lending/aave/**" --offline

Aave v3.6 Tests

  • ProtocolV3TestBase (in utils/ProtocolV3TestBase.sol): base for config snapshots, reserve setup, pool operations tests. Used by Pool.*.t.sol, tokenization tests, rewards, etc.
  • Extensive per-area tests: Pool (supply, borrow, repay, liquidations, flashloans, eMode, rounding), AToken/DebtToken behaviors, ACLManager, oracle, rates, invariants (handler-based + echidna/crytic).
  • Invariants: invariants/ with BaseHandler, ProtocolAssertions, HFPostconditionsSpec, full setup in Setup.t.sol + SpecAggregator.
  • Gas + edge tests in gas/ and protocol/.

Inheritors call parent setups; use mocks under utils/mocks/.

Aave v4 Tests

  • Base (in setup/Base.t.sol): inherits BaseHelpers + BatchTestProcedures. setUp does _etchSetup, _initTokenList, _setupFixtures, _initEnvironment.
  • Orchestration-driven deployment:
    report = AaveV4TestOrchestration.deployTestEnv({ admin: ADMIN, ... });
    hub1 = IHub(report.hubReports[0].hub);
    // then spokes, oracles, etc.
    
  • Separate coverage: hub (supply/withdraw/borrow/repay/configuration/liquidation/risk-premium), spoke, tokenization-spoke (ERC4626 compliance, permits, max getters), position-manager, config-engine, treasury-spoke, access.
  • Gas snapshots, fork verification (deployments/fork/), helpers/mocks for actions and wrappers.
  • Uses deployment procedures + roles procedures for realistic full-init state (aligns LR-7: no address(0) facets/impls).

See AaveV4BatchDeployment.t.sol, procedure tests, and per-feature .t.sol.

Euler Tests

Euler tests are primarily in the ported structure + certora specs (see certora/); direct usage in Crane tests leverages the EVC harnesses and periphery lens for assertions. Combine with CraneTest (from AGENTS) when your test also bootstraps Crane factories/registries:

Inheritance example (pattern from dexes/TestBases, adaptable):

CraneTest
    └── YourLendingTest (attach ports or use direct constructors from port test utils)

Key LR-7 expectations (from PRD): full init before asserts, exact deltas (not just "changed"), Behavior where applicable (for any Crane IFacet layers), registry assertions post-deploy, fork parity (where mainnet oracles/pools exercised).

Use stubs in euler/v1/stubs/ and periphery for mocking. Handlers for stateful (similar to Aave invariants pattern).

Always inherit order correctly and call parent setUp (see AGENTS.md crane-testing patterns).

Example invocation for Aave v4 specific: forge test --match-path "test/foundry/spec/protocols/lending/aave/v4/contracts/spoke/supply/Spoke.Supply.t.sol"

Protocol Utilities

Aave Math + Helpers

  • WadRayMath, MathUtils, PercentageMath, SharesMath (precise interest/liquidity math; used everywhere in accounting).
  • Spoke: SpokeUtils, LiquidationLogic, UserPositionUtils, PositionStatusMap, ReserveFlagsMap, KeyValueList.
  • Hub: AssetLogic, Premium.
  • Other: EIP712 helpers, bytecode utils in deployments.

Euler Math + Periphery

  • EVC: Set.sol (transient set impl), transient storage.
  • Vault: RPow, SafeERC20Lib, LTVUtils, LiquidityUtils, shared cache/snapshot types.
  • Oracle adapters + ScaleUtils.
  • Periphery Lens: AccountLens, VaultLens, OracleLens, UtilsLens (for onchain inspection without side effects).
  • IRM libs, swap QuoteLib/FundsLib/SwapLib.
  • Perspectives + governors for production gating.

Crane-Native Cross-Cutting (usable with lending)

  • Permit2Aware (see contracts/protocols/utils/permit2/aware/ and IPermit2Aware): for signed approvals in deposits etc.
  • IERC4626RateProvider + IRateProvider: for yield tokens (stata, wrappers) in Aave/Euler contexts.
  • General: use with ConstProdUtils where DEX+lending composes; Sets (AddressSet etc) for collections in custom services.
  • From central NatSpec (use ONLY these values in examples/docs):
    • IFacet: facetName() : 0x5b6f4d01, facetInterfaces() : 0x2ea80826, supportsInterface(bytes4) : 0x01ffc9a7
    • IDiamondPackageCallBackFactory interfaceId: 0x949da331
    • Common DFPkg: packageName() : 0xabc8b346, initAccount(bytes) : 0x870d4838, postDeploy(address) : 0x70068fcf

Ports exercise these in their test harnesses (e.g. TokenizationSpoke as ERC4626).

Agent / Consumer Usage + Value (LR-2 / LR-4)

See getting-started.md, deployment/*.md, concepts/*.md for bootstrap.

  • Reuse already-deployed verified lending code (via direct or custom facets) eliminates agent-introduced bugs.
  • Avoid re-deploying heavy protocol bytecode (cost savings).
  • Bootstrap via Create3FactoryDFPkg + reusable DiamondPackageCallBackFactory (central interfaceId 0x949da331; see diamondPackageFactory() : 0x0fe96d13 from ICreate3Factory).
  • Registries (Facet/Package) populated at InitDevService / factory bootstrap allow resolving shared components without hardcoding.
  • Test via CraneTest + port TestBases/handlers; assert exact values + full lifecycle.

Cross-links: AGENTS.md (TestBase chains, Behavior libs, FactoryService salt), PRD LR-2/LR-4/LR-7, CENTRALLY_COMPUTED_NATSPEC_VALUES.md (ONLY source for @custom values), docs/protocols/lending/* subdocs.

For GitBook: this surfaces port details, test usage, and utilities as required.

Verification

After updates, forge build and targeted lending tests (as above). All NatSpec examples use ONLY values from CENTRALLY_COMPUTED_NATSPEC_VALUES.md. No viaIR. Full init in examples/tests.

See also

Utilities Overview

Crane’s contracts/utils/ libraries are shared building blocks for Repos, Services, tests, and protocol ports. Prefer these over reimplementing sets, AMM math, or hashing in every port — that is part of reuse already deployed and verified code at the library level.

What to read first

TopicPage
Address/Bytes/String sets + Repo patternSets and Set Repos
Constant-product AMM mathConstProdUtils & Math
Architecture mapCodebase Map

Collections

  • Sets: AddressSet, Bytes32Set, Bytes4Set, StringSet, UInt256Set under utils/collections/sets/ — storage-oriented, 1-indexed Repo APIs.
  • Arrays / helpers: BetterArrays and related Better* helpers.

Used heavily in ERC2535Repo, registry Repos, handlers, and comparators.

Math

  • ConstProdUtils: shared constant-product quotes, reserve sorting, LP mint/burn helpers (DEX ports + parity tests).
  • Other: BetterMath, fixed-point helpers, SafeCast variants.

Protocol-specific quoters (Uniswap V3/V4, Slipstream, Aerodrome, Camelot) live with those ports but often share ConstProdUtils for V2-style math.

Cryptography & metatx

  • EIP-712 / ECDSA helpers under utils/cryptography/
  • ERC2771Context for trusted forwarders
  • Message hash utilities

Tokens & safety

  • SafeERC20 and related transfer helpers
  • Nonces / short strings where used by token facets

Deployment helpers

  • CREATE2/CREATE3-oriented helpers (Creation, etc.) used by factories
  • BetterEfficientHashLib for deterministic salts: abi.encode(type(X).name)._hash()

Testing helpers

  • Comparators and behavior logging live under contracts/test/ (see Testing Patterns)
  • Transient slot / reentrancy utilities used by access patterns

See also

Sets and Set Repos

Crane provides type-specific set libraries optimized for Diamond storage (mutate storage in place, avoid unnecessary copies).

Types

SetRepoTypical use
AddressSetAddressSetRepoFacet addresses, registry membership
Bytes32SetBytes32SetRepoGeneral 32-byte keys
Bytes4SetBytes4SetRepoSelectors (ERC2535, comparators)
StringSetStringSetRepoNamed registries
UInt256SetUInt256SetRepoNumeric id sets

Path: contracts/utils/collections/sets/.

Storage shape (AddressSet example)

struct AddressSet {
    mapping(address => uint256) indexes; // 0 = absent; values are 1-indexed
    address[] values;
}

Repo operations

Common pattern on *SetRepo libraries:

  • _add / _remove (idempotent membership)
  • _contains, _length, _index, _indexOf
  • _values (often returns storage pointer for gas)
  • _asArray, _range
  • _addAsc / _removeAsc / _sortAsc — ordered variants for deterministic enumeration

Repos typically expose dual overloads: operate on an explicit Storage/set parameter, or on a default layout when applicable.

Why not OpenZeppelin EnumerableSet only?

Crane sets are tuned for:

  • Direct storage mutation in Diamond Repos
  • Ascending/ordered membership for deterministic walks (registries, facets)
  • Multi-value add/remove loops without copying entire sets into memory unnecessarily

Where they appear

  • FacetRegistryRepo — facets by name/interface/function
  • ERC2535Repo — facet addresses + per-facet selector sets
  • DiamondFactoryPackageRegistryRepo — package membership
  • OperableRepo — function operator sets
  • Test handlers and Bytes4SetComparator / comparator repos

Testing tip

Handlers and Behavior libraries often track expected sets in ghost state and assert equality with on-chain membership after fuzz ops. See Testing Patterns.

See also

ConstProdUtils & Math

ConstProdUtils

ConstProdUtils (contracts/utils/math/ConstProdUtils.sol) is Crane’s shared constant-product AMM math library. DEX ports and tests use it so quote/swap math is not reimplemented per protocol.

Typical operations

  • Reserve sorting: _sortReserves (with/without fee variants)
  • Liquidity: mint amounts on deposit; burn/withdraw amounts on exit
  • Quotes: getPurchaseQuote and overloads for exact-in/out with fees and price impact

Usage style (from framework conventions):

using ConstProdUtils for uint256;

uint256 amountOut = amountIn.getPurchaseQuote(reserveIn, reserveOut);

Who consumes it

  • Camelot V2 services and utils
  • Aerodrome / Uniswap V2-style paths
  • Parity tests under test/foundry/spec/utils/math/constProdUtils/ (e.g. purchase quote vs Camelot)

Protocol-specific quoters for concentrated liquidity (Uniswap V3/V4, Slipstream) live with those ports; still prefer shared helpers where math overlaps.

Other math libraries

LibraryRole
BetterMathWider math helpers (including large int support)
Fixed-point libsWAD/ray-style fixed point where used
SafeCast / variantsSafe downcasts

Testing guidance

Prefer exact quote parity against protocol ports or live fork routers rather than approximate tolerances unless the port documents rounding differences. See Testing Patterns and DEX Integrations.

See also

Key Interfaces

Core

  • IFacetfacetName, facetInterfaces, facetFuncs, facetMetadata.
  • IDiamondFactoryPackage — package metadata, facetCuts, calcSalt, initAccount, postDeploy.
  • IDiamond / IDiamondCut / IDiamondLoupe — standard ERC2535 surfaces.
  • IMultiStepOwnable
  • IOperable

Tokens

  • IERC20, IERC20Metadata, IERC20Permit
  • IERC4626 (via packages)

Introspection

  • IERC165
  • IERC8109Introspection

Factories

  • ICreate3Factory
  • IDiamondPackageCallBackFactory

Registries

  • IFacetRegistry
  • IDiamondFactoryPackageRegistry

Concrete implementations and their selectors are declared by the corresponding facets. Packages expose the subset of interfaces that are installed into proxies.

Centrally computed NatSpec values

Canonical function selectors and interface IDs used in Crane NatSpec (@custom:selector, @custom:interfaceid, @custom:signature). Agents and docs must use these values instead of inventing hex strings.

Source of truth for many symbols: interfaces under contracts/interfaces/ and implementations such as contracts/factories/diamondPkg/DFPkgBase.sol and CREATE3 factory interfaces.

IDiamondFactoryPackage / DFPkg

SymbolSignatureSelector
packageNamepackageName()0xabc8b346
facetInterfacesfacetInterfaces()0x2ea80826
facetAddressesfacetAddresses()0x52ef6b2c
packageMetadatapackageMetadata()0xf45469e7
facetCutsfacetCuts()0xa4b3ad35
diamondConfigdiamondConfig()0x65d375b3
calcSaltcalcSalt(bytes)0xd82be56e
processArgsprocessArgs(bytes)0x87c3adb3
updatePkgupdatePkg(address,bytes)0xa9089235
initAccountinitAccount(bytes)0x870d4838
postDeploypostDeploy(address)0x70068fcf

IDiamondPackageCallBackFactory

ItemValue
Interface ID0x949da331
Example deploy selector0xe97fac05

Confirm against IDiamondPackageCallBackFactory in contracts/interfaces/ when adding new symbols.

IFacet (common metadata surface)

SymbolSelector (typical)
Facet metadata helpers0x5b6f4d01, 0x2ea80826, 0x574a4cff, 0xf10d7a75

See Create3FactoryFacet / IFacet for the authoritative list.

ICreate3Factory (sample)

SymbolSignatureSelector
(see interface)ICreate3Factory0x0fe96d13, 0x1cdca5df, 0xa7b62a7f, 0x1f7fe4db

Policy

  1. Prefer values computed from source (cast sig "fn()" or NatSpec already on interfaces).
  2. Do not invent interface IDs; XOR of declared selectors when documenting a new interface.
  3. Historical copies under the external crane-archive repo are not the public SoT.

AI Agent Skills for Crane

Crane ships with a rich library of skills under .claude/skills/. These enable Claude Code, Bankr agents, OpenClaw, Cursor, and other compatible tools to author correct, secure, gas-efficient Diamond contracts using Crane patterns.

Public skills live under .claude/skills/. The tree is curated for Crane product + protocol architecture + Foundry + borderline TS tooling. Personal/Bankr-ecosystem bazaar skills are not tracked in this repository.

Core Crane Skills (Start Here)

  • crane-architecture — Facet-Target-Repo, storage slots, guard functions, AwareRepo, Service, DFPkg rules.
  • crane-deployment — CREATE3, DiamondPackageCallBackFactory, FactoryService, Init*Service, salt conventions.
  • crane-testing — TestBase, Behavior libraries, handlers, invariants, comparators.
  • crane-adversarial-testing — Abuse/attack catalogs for diamonds/vaults.
  • crane-code-style — Headers, naming (_layoutStruct, param_), no viaIR, struct patterns for stack.
  • crane-natspec — Full documentation requirements with include-tags and custom selectors. Values: CENTRALLY_COMPUTED_NATSPEC_VALUES.md.
  • crane-access — Operable, ERC8023 MultiStepOwnable, reentrancy.
  • crane-tokens — ERC20/2612/4626 native implementations + DFPkgs + Permit2 aware.
  • crane-utilities — Math (ConstProdUtils), sets, EIP712, cryptography, pagination.
  • crane-porting — How to vendor protocols into contracts/external + contracts/protocols with shared transitive deps (no private OZ clones).
  • crane-porting-verification — Hermetic/fork tests, Behaviors, and definition-of-done gates for ports.
  • docs-to-skills — Crawl full documentation sites/trees; inventory every page; emit multi-skill families with coverage reports.
  • skill-authoring — Progressive disclosure, description triggers, compartmentalized references/, quality checklists for SKILL.md.

Agent identities

  • crane-porter (.claude/agents/crane-porter.md) — end-to-end protocol porting sessions (vendor, remap, wrap, verify).
  • docs-skill-scribe (.claude/agents/docs-skill-scribe.md) — documentation scrape → progressive-disclosure skill families.

Protocol Skills (Reusable Ports)

Extensive high-quality ports with dedicated skills:

  • Balancer V3 (all pool types: weighted, stable, gyro, ReClamm, COW, hooks, vault ops)
  • Uniswap V2 / V3 / V4 (pools, positions, swaps, hooks, flash accounting)
  • Aerodrome + Slipstream (pools, gauges, CL math, rewards, voter)
  • Aave v3 + v4 Hub/Spoke (full architecture, config, liquidation, tokens, position mgr)
  • Euler (EVC, EVault, risk, oracles, periphery)
  • pons (Robinhood Chain launchpad): pons-architecture, pons-operations, pons-integration (+ pons-family coverage/sources) — docs-derived end-user and integrator skills for v1 (live Uniswap V3) and v2 (curve → V4; addresses pending)
  • And many more (Pendle, Frax ecosystem, Reliquary, Resupply, Comet, Permit2, Chainlink VRF, Olympus, Morpho, Reactive, etc.)

Each protocol skill teaches both the external protocol and how Crane wraps it with services, aware repos, DFPkgs, and test infrastructure.

How Agents Should Use Skills

  1. When the user asks for a feature, call the relevant skill(s) first.
  2. Generate code that follows the documented patterns exactly.
  3. Add NatSpec + tags.
  4. Write accompanying tests using the testing skill.
  5. After delivery, consider contributing an updated or new skill so the knowledge persists for all agents.

Maintaining Skills

Skills live in this repo under .claude/skills/<name>/SKILL.md.

  • Keep them concise but example-rich.
  • Include "references/" sub-files for long examples.
  • Update when the underlying contracts or best practices evolve.
  • Remove or archive stale "copy" directories.

See the root AGENTS.md for broader instructions.

Installable marketplaces

For agents that load skills via Claude Code / Codex / Grok / OpenCode marketplaces:

MarketplaceAudienceInstall
cyotee/cyotee-claude-pluginsDevelopers building on Crane and DeFi protocols/plugin marketplace add cyotee/cyotee-claude-plugins then /plugin install crane@cyotee
cyotee/defi-agent-skillsAgents operating on-chain (cast/Bankr runbooks)/plugin marketplace add cyotee/defi-agent-skills
  • Forge skills (forge-testing, forge-fuzz-testing, forge-deployment)
  • Optional TS/JS agent tooling (tevm, voltaire-effect, wagmi) via the developer marketplace

Using these skills together gives agents a structured way to build and ship modular on-chain software with Crane patterns.


last_reviewed: 2026-08-09 git_sha: 4f9a1412 scope: crane method: cartographer+survey

Crane Codebase Map

Primary structure map for the Crane Diamond framework. Capability checklist: docs/agent/CRANE_CAPABILITY_INVENTORY.md. Task router: docs/agent/AGENT_NAVIGATION_INDEX.md. Maturity: docs/protocols/status.md.

Overview

Crane is a Diamond-first (ERC-2535) Solidity framework: Facet–Target–Repo, *Service libraries, DFPkg diamond factory packages, CREATE3 deterministic deploy, production-first TestBase/Behavior/handler testing, and faithful protocol ports with shared remapped dependencies under contracts/external/.

Stack: Solidity 0.8.35 (see foundry.toml), Foundry, CREATE3, ERC-2535.

crane/
├── AGENTS.md · CLAUDE.md
├── contracts/
│   ├── access/ · factories/ · proxies/ · proxy/ · introspection/
│   ├── tokens/ · utils/ · interfaces/ · registries/ · bounties/
│   ├── protocols/          # Crane wrappers + ports by domain
│   ├── external/           # Vendored upstream + shared OZ/Solady
│   └── test/               # CraneTest + protocol TestBases
├── test/foundry/           # Specs mirroring contracts
├── docs/                   # Product docs + agent/ maps
├── .claude/skills/         # Canonical skills SoT
└── .cartographer/          # Committed code graph

Core packages

AreaPathPurpose
Factoriescontracts/factories/CREATE3, diamond package factories, FactoryService
Accesscontracts/access/Operable, multi-step ownable, reentrancy locks
Proxiescontracts/proxies/, contracts/proxy/Diamond proxy infrastructure
Tokenscontracts/tokens/ERC20/permit/4626 packages
Utilscontracts/utils/Math (ConstProd), sets, crypto, pagination
Introspectioncontracts/introspection/ERC165/2535 helpers
Registriescontracts/registries/On-chain registries
Interfacescontracts/interfaces/Shared interfaces
Init servicescontracts/InitDevService.sol, InitBcService.solDev/bootstrap helpers

Protocols layout

contracts/protocols/
├── lending/     # morpho, aave, euler, …
├── dexes/       # uniswap, aerodrome, …
├── tokens/      # stable/olympus, wrappers, …
├── cdps/        # sky, liquity, …
├── launchpads/  # ponsFamily, uniswap CCA, …
├── l2s/         # superchain
├── oracles/     # chainlink
├── utils/       # permit2, gsn
├── messaging/ · perps/ · wallets/ · staking/

External: contracts/external/** — vendor sources with VENDOR.md; expand shared deps first; remap imports to @crane/contracts/external/....

High-value ports for agents

PortPathSkills
Morpho Blue / MetaMorpho / Vault V2 / Bundlercontracts/protocols/lending/morpho/crane-morpho, morpho-*
Olympus V3 / Default Frameworkcontracts/protocols/tokens/stable/olympus/crane-olympus, olympus-*
Uniswap stackcontracts/protocols/dexes/uniswap/crane-uniswap, uniswap-v*
Balancer V3protocols + externalcrane-balancer, balancer-v3-*
Aerodrome / Slipstreamcontracts/protocols/dexes/aerodrome/crane-aerodrome, slipstream-*

Testing map

PieceLocation
CraneTestcontracts/test/CraneTest.sol
Specstest/foundry/spec/ (mirrors contracts tree)
Port testsunder protocol trees + test/foundry/spec/protocols/**
Skillscrane-testing, crane-adversarial-testing

Docs map

DocRole
docs/SUMMARY.mdDoc index
docs/deployment/, docs/development/Deploy + testing guides
docs/protocols/status.mdMaturity labels
docs/agent/*Agent inventory / navigation (this program)
docs/archive/Historical plans (not active trackers)

Skills & agents

Canonical skills: .claude/skills/. Key framework skills: crane-deployment, crane-architecture, crane-testing, crane-adversarial-testing, crane-porting, crane-porting-verification, crane-access, crane-code-style, crane-natspec, crane-utilities, crane-tokens.

Agents: .claude/agents/crane-porter.md, docs-skill-scribe.md.

Cartographer

Committed under .cartographer/ (no Git LFS). Install CLI via consumer repo installer or Claude marketplace + Bun. Re-index with --force; require verify --fresh.

Consumers

IndexedEx (and others) should:

  1. Point harnesses at this map + capability inventory
  2. Pin submodule gitlink to a pushed Crane SHA that includes these docs
  3. Sync skills with consumer sync scripts when needed

Archive policy

This directory holds historical or non-product material that should not appear in the public docs navigation (docs/SUMMARY.md) or mdBook site.

What stays in this repo

PathPurpose
internal-plans/Small set of historical PRDs, porting notes, and funding/governance drafts moved off the repo root
audits/Third-party audit PDFs kept thin in-tree for convenience

Bulk history (external)

Large generated bulk (gap-report mirrors, HTML research scrapes) lives in a separate repository so clones of Crane stay product-focused:

https://github.com/cyotee/crane-archive

Do not re-import those trees into this default branch.

Rules

  1. Product documentation belongs under docs/ with a SUMMARY.md entry — not under archive/.
  2. Prefer short, curated notes over dumping agent session logs.
  3. Funding / token narratives are not part of the framework front door; historical drafts may remain under internal-plans/.

Changelog

Unreleased

Public packaging

  • Remove in-repo CRANE task system and agent session dumps from the default branch.
  • Move historical planning/funding notes under docs/archive/internal-plans/.
  • Externalize bulk gap reports and research scrapes to cyotee/crane-archive.
  • Add SECURITY.md, CONTRIBUTING.md, NOTICE.md, .env.example.
  • Rewrite README and getting-started for a framework-first public surface.
  • Publish protocol maturity status and public NatSpec values reference.
  • Curate agent skills: keep Crane/protocol/Foundry/borderline tooling; remove bazaar noise.

Config

  • Align agent docs with Solidity 0.8.35 (Foundry pin).
  • Fix OpenZeppelin upgradeable remapping typo (@ozu/).
  • Prefer npm lockfile; document yarn as non-canonical if both remain temporarily.