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).
- Getting Started — install, agent reuse, map of required topics
- Concepts: architecture and patterns (Facet-Target-Repo, Registries, DFPkg)
- Development: style, documentation, and testing conventions
- Deployment: CREATE3, packages, factory services
- Utilities: overview, sets, ConstProdUtils
- Access Control and Tokens: ready-to-use building blocks
- Protocols: DEX, lending
- Reference: interfaces, agent skills, codebase map
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
| Topic | Page |
|---|---|
| CREATE3 / new chain / factory reuse | CREATE3 & New Chain Setup |
| DFPkg operations | Diamond Factory Packages · DFPkg Pattern |
| Registries | Registries |
| Testing / TestBases | Testing Patterns |
| DEX / lending ports | DEX Integrations · Lending |
| Sets / ConstProdUtils | Utilities Overview · Sets · Math |
| Building modules | Building with Crane |
| Architecture map | Codebase Map |
| AI agent skills | Agent 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
- Load skills — Prefer in-repo
.claude/skills/and/or install marketplaces:- Developer: cyotee/cyotee-claude-plugins →
crane@cyotee - Ops: cyotee/defi-agent-skills (on-chain runbooks; not architecture dumps)
- Developer: cyotee/cyotee-claude-plugins →
- Start with
crane-architecture,crane-deployment, andcrane-testing. - Use Facet-Target-Repo + DFPkg for new features — Repo for storage, Target for logic, Facet for Diamond exposure.
- Bootstrap via
Init*Serviceor Crane test bases — avoid ad-hocnewfor production deployment paths. - Follow NatSpec conventions in AGENTS.md and NatSpec docs.
- Optional BattleChain gate — significant factories/packages may be exercised on BattleChain before mainnet promotion; see BattleChain.
Next steps
- Read Building with Crane
- Skim Facet-Target-Repo
- Try CREATE3 setup and DFPkg
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:
-
*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.
- Use dual
-
*Target.sol(contract) — Business logic that inherits interfaces and calls into the Repo.- Thin; delegates heavy lifting to Repo.
-
*Facet.sol(contract) — Extends Target and implementsIFacetfor 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
-
Define the interface(s) first (
contracts/interfaces/...).- Add full NatSpec +
@custom:selector/signature/topiczero. - Wrap with
// tag::IFoo[] ... // end::IFoo[].
- Add full NatSpec +
-
Create the Repo.
- Dual functions for every operation.
_onlyXxx(Storage storage, ...)guards.- NatSpec + include tags.
-
Implement Target + tests.
- Use Behavior libraries for interface compliance.
- Write handler + invariant tests for stateful properties.
-
Create the Facet.
- Implement
facetName,facetInterfaces,facetFuncs,facetMetadata. - Add NatSpec.
- Implement
-
(Recommended) Create a DFPkg.
PkgInitandPkgArgslive in the interface.- Provide a
deploy(...)helper. - Wire
initAccountto apply cuts + call initializers.
-
Write a FactoryService (if multiple related deploys).
- Group facet + package deployment.
- Always
vm.label. - Salt =
abi.encode(type(X).name)._hash().
-
Add or update a skill under
.claude/skills/.- Document usage, examples, gotchas.
- Make it usable by other agents.
-
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:signatureand the matching selector (usecast 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_*.solandBehavior_*.solnext 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+*Servicerather than reinventing. - After shipping, package the knowledge into a new or updated skill.
- Use
CraneTestbase 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
| Layer | File Suffix | Responsibility | Deployed? |
|---|---|---|---|
| Repo | *Repo.sol | Storage layout and access functions | No |
| Target | *Target.sol | Business logic implementation | No |
| Facet | *Facet.sol | Diamond exposure and metadata | Yes |
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
bytes32constant 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 layoutStructas 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,
Storagestructs, and_layoutStructoverloads 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)
Cross-Links to Required GitBook Areas (LR-2)
Per PRD LR-2, storage slots are foundational to these required content areas:
- Chain setup via CREATE3 Package: Deploy your own Create3Factory using
Create3FactoryDFPkg(andICREATE3DFPkg). 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
0x949da331from 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 viaCraneTest+TestBase_*inheritance (e.g.TestBase_CamelotV2,TestBase_BalancerV3Vault) + handlers/Behaviors. See protocol docs underdocs/protocols/, development/testing.md, and AGENTS.md testing section. - General utilities (Sets, ConstProdUtils, collections):
AddressSet,Bytes4Set, etc. (and their*SetRepohelpers) are stored inside many RepoStoragestructs (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:
initAccountruns 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:
| Function | Role |
|---|---|
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()—0xabc8b346facetCuts()—0xa4b3ad35initAccount(bytes)—0x870d4838postDeploy(address)—0x70068fcf
Conceptual vs operational docs
| Doc | Focus |
|---|---|
| This page | What a DFPkg is, struct rules, package lifecycle concepts |
| Diamond Factory Packages | Operational package construction and deploy steps |
| CREATE3 & New Chain Setup | Factories, chain bootstrap, DPCF reuse |
| Registries | How packages/facets are discovered after deploy |
Lifecycle (high level)
- Deploy facets once with Create3Factory (registered automatically when using factory deploy helpers).
- Deploy a package whose constructor stores immutable facet addresses (
PkgInit). - Deploy proxies via
DiamondPackageCallBackFactory.deploy(pkg, pkgArgs)— the package supplies cuts + init. - Consumers resolve packages later via Package Registry
canonicalPackage(interfaceId)when available.
See also
- Diamond Factory Packages (deployment)
- CREATE3 & New Chain Setup
- Building with Crane
- Getting Started
- Tokens: ERC20 DFPkg
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
- Bootstrap factories (
CraneTest/InitDevService). - Deploy facets/packages through Create3 helpers (auto-register).
- Resolve with
canonicalFacet/canonicalPackagein DFPkg constructors and init paths. - 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():0xabc8b346facetInterfaces():0x2ea80826facetAddresses():0x52ef6b2cfacetCuts():0xa4b3ad35diamondConfig():0x65d375b3calcSalt(bytes):0xd82be56einitAccount(bytes):0x870d4838postDeploy(address):0x70068fcfdeployCreate3Factory(address):0x34cb11b5
Bootstrap Flow for New Chain
- Obtain/deploy an initial
Create3Factoryentrypoint (barenew Create3Factory{salt}(owner)as inInitDevService.initFactory). - Use bootstrap to deploy canonical core facets (Cut, Ownable, Operable, Create3 facet, registry facets, etc.).
- Wire the reusable
DiamondPackageCallBackFactory(see reuse note; selector0x1cdca5dffor set). - Deploy (or obtain) the
Create3FactoryDFPkgpassing the facets + the shareddiamondFactoryinPkgInit. - Call
deployCreate3Factory(owner)on the DFPkg (internally delegates todiamondFactory.deployusingPkgArgs):
// 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 viapackageMetadata())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).
Using Factories in Protocol Tests and Utilities (Cross-Links)
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.solforcreate3Factory.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(seecontracts/utils/math/ConstProdUtils.soland tests undertest/foundry/spec/utils/math/constProdUtils/) - DEX-specific services:
CamelotV2Service, Balancer router helpers, etc. (incontracts/protocols/dexes/*/services/) - Stubs for mocks: in protocol
stubs/and test bases. - General type libs (used across):
AddressSetRepo,Bytes32SetRepoetc. (seecontracts/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
- Registries (concept)
- DFPkg Pattern
- Diamond Factory Packages
- Factory Services
- Getting Started
- Testing Patterns
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
- Caller invokes
factory.deploy(pkg, pkgArgs). - Factory calls
pkg.calcSalt(pkgArgs)to obtain the CREATE2 salt for the proxy. - If a proxy already exists at the computed address, it is returned immediately.
- Otherwise the factory deploys a
MinimalDiamondCallBackProxyvia CREATE2. - The proxy calls back into the factory.
- The factory stores the package and arguments, then delegatecalls
pkg.initAccount(processedArgs)on the proxy. - The package performs all storage initialization (setting owners, writing token metadata, etc.).
- The factory calls
pkg.postDeploy(proxy). - Optional post-deploy hook facet is removed.
- The proxy address is returned.
The package never holds proxy state. It only supplies cuts and performs initialization.
Reuse Characteristics
- One
ERC20Facetdeployment serves every ERC20 proxy created fromERC20DFPkgon 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
- DFPkg Pattern (concepts)
- CREATE3 & New Chain Setup
- Registries
- Getting Started
- ERC20 + Permit + DFPkg
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…1f42 | Use |
| Uniswap V3 Factory / SwapRouter / NPM | Use |
| USDC, DAI, Chainlink mocks, Safe, … | Use when needed |
| Uni V2, Uni V4 PoolManager, Permit2, Crane factories | Deploy via Crane create3 |
Source: BattleChain mock & dependency contracts.
Process
- Implement + test locally (Foundry invariants, behavior tests).
- Use
InitBcService(BattleChain-aware bootstrap) in deployment scripts. - Deploy to BattleChain testnet.
- Create Safe Harbor agreement (scope the Create3Factory with appropriate child contract scope).
- Enable attack mode and monitor.
- Survive testing → promote to PRODUCTION on BattleChain.
- 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).
- Deployed Addresses
- Machine JSON:
addresses/battlechain-sepolia.json - Solidity:
contracts/constants/networks/BC_TESTNET.sol(CREATE3_FACTORY,WETH, Uni V2/V3/V4, etc.)
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:
| File | Purpose |
|---|---|
addresses/battlechain-sepolia.json | JSON for agents / tooling |
addresses/battlechain-sepolia.table.md | mdBook 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)
| Field | Value |
|---|---|
| Network alias | battlechain-sepolia |
| Chain ID | 627 |
| RPC | https://testnet.battlechain.com |
| Explorer | explorer.testnet.battlechain.com |
| Deploy script | scripts/foundry/Script_Promo_BC_Launch.s.sol |
| Wave | A — Crane core + ERC20Permit + Uni V2/V4 + Permit2 + Safe Harbor |
| Policy | Use BC-provided deps; do not replace (WETH, Uni V3, …) |
| Deployer EOA | 0xF71ea560c6465727efFe07Cfb4e1a05B40520Dd7 |
| Deployed at block | 17158 |
| Status | Deployed (Wave A live) |
Addresses (Wave A)
| Component | Address |
|---|---|
| Create3Factory (core) | 0x0792632343b6a31e4606452aeb50F18A2DE14c27 |
| DiamondPackageCallBackFactory | 0x12C636556d0e5e5c0f2006583E32AcA49c8d4Ffb |
| ERC20Facet | 0x3547a3e83E88c6b080b2d3202a3c3e062A43b196 |
| ERC5267Facet | 0xe09934dA5103B49D59d67DE904bda7CBd3fB07E8 |
| ERC2612Facet | 0x8C52034D050D81807d77de91251d14BbE9AA7Fac |
| ERC20PermitDFPkg | 0xDeE300dc0708DeD0517f54e70Ac2ddfDaAE3142A |
| 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 agreement | 0xEA85951dA0600989CbbCAbd5CB98212c57410F55 |
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) withChildContractScope.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_TESTNETfor 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
- BattleChain Security Gate
- CREATE3 & New Chain Setup
- IndexedEx operator plan:
docs/BATTLECHAIN_DEPLOY_PLAN.md(in the IndexedEx repo)
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:
- External libraries (
@openzeppelin,@solady). - Crane interfaces (
@crane/contracts/interfaces/...). - Crane contracts (
@crane/contracts/...). - Test utilities (only in test files).
Use the defined remappings:
@crane/@solady/@openzeppelin/forge-std/
Function Order
Within each contract or library:
- Constructor
- Receive / Fallback
- External
- Public
- Internal
- Private
Naming
| Element | Convention | Example |
|---|---|---|
| Storage access | _layoutStruct() | _layoutStruct(), _layoutStruct(bytes32) |
| Initialization | _initialize(...) | _initialize(address owner_) |
| Internal state functions | _functionName(...) | _isOperator(address) |
| Guard functions | _onlyXxx(...) | _onlyOperator() |
| Modifiers | onlyXxx | onlyOperator |
| Storage parameter | layoutStruct | Storage storage layoutStruct |
| All parameters | trailing underscore | owner_, 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
viaIRandvia_irmust remain disabled.- Stack-too-deep errors are resolved by grouping parameters and intermediate values into
structtypes passed bymemoryorcalldata. - 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.solcontracts/access/ERC8023/MultiStepOwnableTarget.solcontracts/access/ERC8023/MultiStepOwnableRepo.solcontracts/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 Type | Tag | Value Type | Example Computation |
|---|---|---|---|
| Function | @custom:signature | string | See Verification Script / Central Values |
| Function | @custom:selector | bytes4 | See Verification Script / Central Values |
| Error | @custom:signature | string | See Verification Script / Central Values |
| Error | @custom:selector | bytes4 | See Verification Script / Central Values |
| Event | @custom:signature | string | (optional, e.g. @custom:topic-signature) |
| Event | @custom:topiczero | bytes32 | See Verification Script / Central Values |
| Interface | @custom:interfaceid | bytes4 | type(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
castcommands or manual math).
- The script must output compiler-computed values for interface IDs (using
type(I).interfaceIdwhere 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).interfaceIdfor IDs andkeccak256(via Solidity) inside the compiled contract for selectors and topic0. - Output appears in console; copy the exact
0x...bytes intoCENTRALLY_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):
- Find the symbol in the relevant gap report under
docs/archive/reports/gap/. - Insert the
@custom:lines using ONLY the pre-computed values from that file. - Wrap with the exact
// tag::...[]/// end::...[]as per gold standard. - Verify with
forge buildand 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:throwswhere applicable. - Repos must document both the parameterized (
Storage storage layoutStruct) and default overload versions (with distinct hyphenated tags). - Targets and Facets should use
@inheritdocwhere they delegate, plus their own tags for clarity. - Facets must fully implement
IFacetwith documentedfacetName(),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()andfacetFuncs()(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 buildand 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-natspecskill (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:
- Testing Patterns (Behavior, TestBase, declaration tests)
- Diamond Factory Packages, CREATE3, Factory Services
- Facet-Target-Repo, Storage Slots
- AI Agent Skills
- Repo-root
PRD.md(LR-1, LR-2, LR-3, LR-7) - Archived tracking:
docs/archive/reports/gap/(includes formerGAP_REPORTgap tree) - Gold standard ERC8023 sources
Related Files
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.solandTestBase_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_*andBehavior_*live incontracts/.- 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 Bootstrap + Registries (Cross-link to Required GitBook Areas)
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 V3TestBase_BalancerV3Vault+ realBalancerV3VaultDFPkg+ pool DFPkg bases). - Lending:
docs/protocols/lending.md(AaveProtocolV3TestBase+ Aave v4Base+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) usevm.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 priorexpect_*(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():0x5b6f4d01facetInterfaces():0x2ea80826facetFuncs():0x574a4cfffacetMetadata():0xf10d7a75supportsInterface(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 + General Utilities in Tests (Cross-Links)
Protocol-specific:
- DEX services +
ConstProdUtils: Camelot/Aerodrome/Uniswap V2 useConstProdUtils._saleQuote/_purchaseQuoteetc for parity checks against live router/pool execution inside TestBases. Seedocs/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/*.
Recommended Flow (for new tests / agent ports)
- Inherit or extend appropriate
TestBase_*(call parentsetUp()first). - For interface compliance/declaration: implement control virtuals + inherit
TestBase_IFacet(or callBehavior_IFacetdirectly). Assert length +areValid_*. - For stateful/invariants: implement Handler (normalize,
expect*, track ghosts), register in setUp, writeinvariant_*exact asserts. - Use Behavior libs for standards; comparators for collections.
- Full init (real facets), exact deltas, NatSpec on public test surface (with central values +
// tag::Name[]). - 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)
- Registries, CREATE3 bootstrap, DFPkg reuse: CREATE3, DFPkg deploy, Registries, DFPkg pattern
- Ported protocols + TestBase/handler details: DEX Integrations, Lending
- General utilities + Sets: Utilities Overview, Sets, ConstProdUtils
- Core patterns + full examples:
AGENTS.md(Testing section) - NatSpec process: NatSpec
- Getting started / agent reuse: Getting Started
- Source roots:
contracts/test/,contracts/*/TestBase_*.sol,contracts/*/Behavior_*.sol,test/foundry/spec/ - Skills:
crane-testing, protocol-specific skills
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.soltest/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
- Current owner calls
transferOwnership(newOwner). - A pending owner is recorded together with a timestamp.
- 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
Related
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.OperableTargetOperableFacetOperableModifiers
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.20for balances, allowances, and metadata.eip.erc.2612for 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
ERC20PermitDFPkgadds EIP-2612 permit support.ERC4626*DFPkgvariants 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.
| Label | Meaning |
|---|---|
| stable | Crane-native patterns in active use; TestBase/Behavior coverage expected for public APIs |
| experimental | Usable for integration/learning; APIs or ports may change; not a production guarantee |
| vendored | Upstream sources under contracts/external or protocol trees; fidelity to upstream, Crane wrappers partial |
| WIP | Incomplete port or scaffolding; do not rely on for mainnet |
Core framework
| Area | Path | Maturity |
|---|---|---|
| CREATE3 factory + DFPkg | contracts/factories/ | stable |
| Diamond package factory | contracts/factories/diamondPkg/ | stable |
| Access (Operable, ERC8023 MultiStepOwnable, reentrancy) | contracts/access/ | stable |
| Tokens (ERC20/2612/4626 + DFPkgs) | contracts/tokens/ | stable |
| Registries | contracts/registries/ | stable |
| Introspection (ERC165/2535 helpers) | contracts/introspection/ | stable |
| Utils (math, sets, crypto) | contracts/utils/ | stable (some TODOs remain) |
| InitDev / InitBc services | contracts/Init*.sol | stable |
| Bounties DFPkg | contracts/bounties/ | experimental (product-adjacent) |
DEX / AMM ports
| Protocol | Maturity | Notes |
|---|---|---|
| Uniswap V2/V3/V4 services & wrappers | experimental – vendored | Skills + partial Crane services; verify TestBases per path |
| Balancer V3 | experimental – vendored | Vault/pool integration skills; port depth varies |
| Aerodrome + Slipstream | experimental – vendored | Base-focused; gauge/CL surfaces |
| Camelot | experimental | Service wrappers present |
Lending / CDP / other
| Protocol | Maturity | Notes |
|---|---|---|
| Aave V3 | vendored / experimental wrappers | Large vendor tree |
| Aave V4 Hub/Spoke | WIP – experimental | Port in progress; not production-complete |
| Euler EVC/EVK | vendored / experimental | |
| Compound Comet | vendored / experimental | Skills available |
| Resupply | experimental | |
| Reliquary | experimental | |
| Pendle / Frax / Liquity / Sky | vendored / WIP | Large trees; use status carefully |
| Reactive Network demos | experimental | Messaging 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
AwareRepolibraries for dependency injection (routers, factories, vaults). - Stateless
*Servicelibraries 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:
- Architecture: Codebase Map and AGENTS.md (protocol structure + TestBase inheritance).
- Lifecycle details: Balancer V3 Lifecycle, Uniswap V4 Lifecycle.
- Testing patterns: Testing Patterns, crane-testing skill.
- Shared math: ConstProdUtils & Math.
- Deployment reuse: DiamondPackageCallBackFactory (interfaceId
0x949da331) is intended for public reuse across chains; see CREATE3 and DFPkg (e.g.packageName()selector0xabc8b346).
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.soltest/foundry/spec/utils/math/constProdUtils/ConstProdUtils_calculateFeePortionForPosition_Aerodrome.t.soltest/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. UsesConstProdUtils, referrer support, fee-on-transfer handling.stubs/:CamelotFactory,CamelotRouter,CamelotPair(plus UniswapV2ERC20 + libs).
Test usage:
- Inherit
TestBase_CamelotV2(which inheritsTestBase_Weth9). - Calls
TestBase_Weth9.setUp()then deploysCamelotFactory(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_Camelotcreates balanced/unbalanced/extreme pools + tokens usingERC20PermitMintableStub, 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 forinvariant_*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.solservices/UniswapV2Service.sol— swap/quote helpers usingConstProdUtils._saleQuote, deposit/sale helpers.libraries/TransferHelper.sol;stubs/:UniV2Factory,UniV2Router02,UniV2Pair+ deps.
Test usage:
TestBase_UniswapV2(inheritsTestBase_Weth9) deploys fee setter + stubs for factory/router.- Provides
addBalancedUniswapLiquidity(...)helper (usesConstProdUtils._equivLiquidity+sortedReserves+ deal/approve). TestBase_UniswapV2_Poolsextends 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(inheritsTestBase_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.solservices/:AerodromeService.sol,AerodromeServiceVolatile.sol,AerodromeServiceStable.sol(useConstProdUtilsfor 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(inheritsTestBase_Weth9): deploys the entire protocol stack (AERO token first, factories, voter, distributor, minter, router, art proxy, esrow, gauges, rewards, governors). Sets labels.TestBase_Aerodrome_Poolsextends 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_SlipstreamForkfor 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_BalancerV3→BaseTest(ported minimal Balancer test utils for tokens, timestamps, helpers).TestBase_BalancerV3Vault(usesCraneTestindirectly via deployers + mocks, deploys realBalancerV3VaultDFPkgin some paths, RouterMock/Buffer/etc.,VaultContractsDeployer).- Specialized:
TestBase_BalancerV3_WeightedPool,TestBase_BalancerV3_8020WeightedPool, router base. - Mocks (dozens):
ERC20TestToken,PoolHooksMock,RateProviderMock,RouterMock,ArrayHelpers, etc. live intest/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_IFacetpatterns (seecontracts/factories/diamondPkg/{TestBase_IFacet,Behavior_IFacet}.solandtest/foundry/spec/factories/diamondPlg/IFacet_Behavior_Test.sol). - Verify
facetName()(selector0x5b6f4d01),facetInterfaces()(0x2ea80826),facetFuncs()(0x574a4cff),facetMetadata()(0xf10d7a75), plus protocol interfaces.
- Many
- 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 realBalancerV3VaultDFPkgvianew+ pkg init usingIBalancerV3VaultDFPkg.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)
-
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_*Forkvariants (use withvm.createSelectFork+ network consts).
- Unit/isolated:
-
Stubs vs real:
- Stubs (in
*/stubs/) for fast, hermetic tests. Deployed inside TestBase ifaddress(xxx) == address(0). - Never use stubs on fork.
- Stubs (in
-
Behavior / declaration testing for facetized DEX parts:
- Balancer pools/vault/router facets declare IFacet surface.
- Prefer
Behavior_IFacethelpers +expect_*/hasValid_*(or direct parity tests in *IFacet.t.sol). - Full initialization required (real facet addresses passed to DFPkgs, never 0).
-
Services + quoting:
- Use
*Service._xxx(...)for expected behavior in tests (compare to direct router +ConstProdUtils). - Example from Camelot tests: compute via
_saleQuotethen execute and assert equality + events/balances.
- Use
-
Invariant / handler testing:
- Camelot example:
CamelotV2Handler+targetContract+invariant_*(K tracking).
- Camelot example:
-
Consumers using AwareRepos + DFPkgs:
- In your Diamond consumer:
CamelotV2RouterAwareRepo._initialize(routerFromTestBase); - Deploy consumer via
diamondPackageFactoryfromCraneTest. - For Balancer: pass vault-aware facets into pool DFPkg
PkgInit.
- In your Diamond consumer:
-
Cross-protocol:
- All V2/volatile use
ConstProdUtils. - CL (UniswapV3/Slipstream) share tick/sqrt math.
- Balancer has its own fixed-point + scaling (ported).
- All V2/volatile use
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)
Cross-Links & Next Steps
- 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.solcontracts/protocols/lending/aave/v4/spoke/TokenizationSpoke.solcontracts/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). UsesinitOperation, 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/Permit2AwareTargetfor gasless approvals (see tokens/ERC4626/* and l2s relayers for examples). Rate providers viaIERC4626RateProviderfor 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
IDiamondFactoryPackageselectors 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(inutils/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(insetup/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/andIPermit2Aware): 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
- IFacet:
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() : 0x0fe96d13from 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
| Topic | Page |
|---|---|
| Address/Bytes/String sets + Repo pattern | Sets and Set Repos |
| Constant-product AMM math | ConstProdUtils & Math |
| Architecture map | Codebase Map |
Collections
- Sets:
AddressSet,Bytes32Set,Bytes4Set,StringSet,UInt256Setunderutils/collections/sets/— storage-oriented, 1-indexed Repo APIs. - Arrays / helpers:
BetterArraysand 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,SafeCastvariants.
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/ ERC2771Contextfor trusted forwarders- Message hash utilities
Tokens & safety
SafeERC20and related transfer helpers- Nonces / short strings where used by token facets
Deployment helpers
- CREATE2/CREATE3-oriented helpers (
Creation, etc.) used by factories BetterEfficientHashLibfor 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
| Set | Repo | Typical use |
|---|---|---|
AddressSet | AddressSetRepo | Facet addresses, registry membership |
Bytes32Set | Bytes32SetRepo | General 32-byte keys |
Bytes4Set | Bytes4SetRepo | Selectors (ERC2535, comparators) |
StringSet | StringSetRepo | Named registries |
UInt256Set | UInt256SetRepo | Numeric 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/functionERC2535Repo— facet addresses + per-facet selector setsDiamondFactoryPackageRegistryRepo— package membershipOperableRepo— 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:
getPurchaseQuoteand 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
| Library | Role |
|---|---|
BetterMath | Wider math helpers (including large int support) |
| Fixed-point libs | WAD/ray-style fixed point where used |
SafeCast / variants | Safe 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
IFacet—facetName,facetInterfaces,facetFuncs,facetMetadata.IDiamondFactoryPackage— package metadata,facetCuts,calcSalt,initAccount,postDeploy.IDiamond/IDiamondCut/IDiamondLoupe— standard ERC2535 surfaces.IMultiStepOwnableIOperable
Tokens
IERC20,IERC20Metadata,IERC20PermitIERC4626(via packages)
Introspection
IERC165IERC8109Introspection
Factories
ICreate3FactoryIDiamondPackageCallBackFactory
Registries
IFacetRegistryIDiamondFactoryPackageRegistry
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
| Symbol | Signature | Selector |
|---|---|---|
packageName | packageName() | 0xabc8b346 |
facetInterfaces | facetInterfaces() | 0x2ea80826 |
facetAddresses | facetAddresses() | 0x52ef6b2c |
packageMetadata | packageMetadata() | 0xf45469e7 |
facetCuts | facetCuts() | 0xa4b3ad35 |
diamondConfig | diamondConfig() | 0x65d375b3 |
calcSalt | calcSalt(bytes) | 0xd82be56e |
processArgs | processArgs(bytes) | 0x87c3adb3 |
updatePkg | updatePkg(address,bytes) | 0xa9089235 |
initAccount | initAccount(bytes) | 0x870d4838 |
postDeploy | postDeploy(address) | 0x70068fcf |
IDiamondPackageCallBackFactory
| Item | Value |
|---|---|
| Interface ID | 0x949da331 |
Example deploy selector | 0xe97fac05 |
Confirm against IDiamondPackageCallBackFactory in contracts/interfaces/ when adding new symbols.
IFacet (common metadata surface)
| Symbol | Selector (typical) |
|---|---|
| Facet metadata helpers | 0x5b6f4d01, 0x2ea80826, 0x574a4cff, 0xf10d7a75 |
See Create3FactoryFacet / IFacet for the authoritative list.
ICreate3Factory (sample)
| Symbol | Signature | Selector |
|---|---|---|
| (see interface) | ICreate3Factory | 0x0fe96d13, 0x1cdca5df, 0xa7b62a7f, 0x1f7fe4db |
Policy
- Prefer values computed from source (
cast sig "fn()"or NatSpec already on interfaces). - Do not invent interface IDs; XOR of declared selectors when documenting a new interface.
- 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 intocontracts/external+contracts/protocolswith 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, compartmentalizedreferences/, 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-familycoverage/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
- When the user asks for a feature, call the relevant skill(s) first.
- Generate code that follows the documented patterns exactly.
- Add NatSpec + tags.
- Write accompanying tests using the testing skill.
- 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:
| Marketplace | Audience | Install |
|---|---|---|
| cyotee/cyotee-claude-plugins | Developers building on Crane and DeFi protocols | /plugin marketplace add cyotee/cyotee-claude-plugins then /plugin install crane@cyotee |
| cyotee/defi-agent-skills | Agents operating on-chain (cast/Bankr runbooks) | /plugin marketplace add cyotee/defi-agent-skills |
Related tools
- 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
| Area | Path | Purpose |
|---|---|---|
| Factories | contracts/factories/ | CREATE3, diamond package factories, FactoryService |
| Access | contracts/access/ | Operable, multi-step ownable, reentrancy locks |
| Proxies | contracts/proxies/, contracts/proxy/ | Diamond proxy infrastructure |
| Tokens | contracts/tokens/ | ERC20/permit/4626 packages |
| Utils | contracts/utils/ | Math (ConstProd), sets, crypto, pagination |
| Introspection | contracts/introspection/ | ERC165/2535 helpers |
| Registries | contracts/registries/ | On-chain registries |
| Interfaces | contracts/interfaces/ | Shared interfaces |
| Init services | contracts/InitDevService.sol, InitBcService.sol | Dev/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
| Port | Path | Skills |
|---|---|---|
| Morpho Blue / MetaMorpho / Vault V2 / Bundler | contracts/protocols/lending/morpho/ | crane-morpho, morpho-* |
| Olympus V3 / Default Framework | contracts/protocols/tokens/stable/olympus/ | crane-olympus, olympus-* |
| Uniswap stack | contracts/protocols/dexes/uniswap/ | crane-uniswap, uniswap-v* |
| Balancer V3 | protocols + external | crane-balancer, balancer-v3-* |
| Aerodrome / Slipstream | contracts/protocols/dexes/aerodrome/ | crane-aerodrome, slipstream-* |
Testing map
| Piece | Location |
|---|---|
| CraneTest | contracts/test/CraneTest.sol |
| Specs | test/foundry/spec/ (mirrors contracts tree) |
| Port tests | under protocol trees + test/foundry/spec/protocols/** |
| Skills | crane-testing, crane-adversarial-testing |
Docs map
| Doc | Role |
|---|---|
docs/SUMMARY.md | Doc index |
docs/deployment/, docs/development/ | Deploy + testing guides |
docs/protocols/status.md | Maturity 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:
- Point harnesses at this map + capability inventory
- Pin submodule gitlink to a pushed Crane SHA that includes these docs
- 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
| Path | Purpose |
|---|---|
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
- Product documentation belongs under
docs/with aSUMMARY.mdentry — not underarchive/. - Prefer short, curated notes over dumping agent session logs.
- 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.