crane-porting
This skill should be used when the user asks to "port a protocol", "vendor into Crane", "port code to Crane", "import upstream contracts", "remap dependencies", "contracts/external", "protocol port",
SKILL.md.claude/skills/crane-porting/SKILL.md
GitHubCrane Protocol Porting
How to bring external DeFi protocol code into Crane as a faithful port with shared, non-duplicated transitive dependencies.
Companion skills: crane-porting-verification (tests/forks/DoD gates), crane-architecture, crane-code-style, crane-testing, writing-skills.
Core principle (non-negotiable)
Port each protocol's own contracts faithfully; rewrite their dependency imports to Crane's already-vendored equivalents.
| Layer | What goes here | Mutate logic? |
|---|---|---|
| Domain contracts | Protocol market/AMM/vault/CDP/token logic | No — byte-for-logic equivalent to a pinned upstream tag |
| Shared deps | OZ, Solady, Solmate pieces, Permit2, forge-std, common oracles | Do not re-vendor; remap imports to existing @crane/contracts/external/... |
| Protocol-unique math/infra | e.g. Aave WadRayMath, Uniswap TickMath, BGD upgradeability | Stay with the protocol (not under a fake dependencies/oz tree) |
| Crane wrappers | Service / AwareRepo / Facet-Target-Repo / DFPkg / interfaces | New Crane-native code |
This is the difference between a useful Crane port and a bloated fork of the universe.
Two trees (know which is which)
contracts/
├── external/ # Vendored upstream + shared framework deps
│ ├── openzeppelin-contracts/ # Canonical OZ (non-upgradeable)
│ ├── openzeppelin-contracts-v4/ # When v4 semantics are required
│ ├── openzeppelin-contracts-v5/
│ ├── openzeppelin-upgradeable*/ # Upgradeable OZ variants
│ ├── solady/
│ ├── solmate/
│ ├── uniswap/ # Uni core/periphery source trees
│ ├── balancer/
│ ├── lido/ # Protocol domain vendored here (example)
│ ├── aragon/ # Transitive unique dep of Lido
│ └── … # Other shared / protocol-domain trees
│
└── protocols/ # Crane integration + wrappers + stubs + tests
├── dexes/{protocol}/{version}/
├── lending/{protocol}/{version}/
├── staking/{protocol}/…
├── tokens/…
├── oracles/…
└── …
contracts/external/ — vendored source of truth
- Shared frameworks (OZ, Solady, Solmate, forge-std pieces): one canonical tree, many consumers.
- Protocol domain sources when the full tree is needed for compile/redeploy (e.g.
external/lido/,external/uniswap/v3-core/). - Unique transitives of a protocol (e.g. Aragon for Lido) live as their own top-level under
external/<name>/, not nested inside the protocol as a private OZ clone. - Every vendored package should have a
VENDOR.md(see template below).
contracts/protocols/ — Crane surface
Typical layout (DEX example from AGENTS.md):
protocols/{category}/{protocol}/{version}/
├── interfaces/ # Crane-facing + thin I* re-exports if needed
├── services/ # Stateless *Service libraries for Diamond consumers
├── aware/ # *AwareRepo for router/factory/vault addresses
├── stubs/ # Protocol-faithful impls for hermetic local deploy (NOT mocks)
├── libraries/ # Protocol-unique shared math (only if not already in external)
├── test/bases/ # TestBase_* setup chains
└── (optional) diamond/ # Facet-Target-Repo + DFPkg when in-Diamond use is intended
Do not dump a second full OpenZeppelin under protocols/.../dependencies/. That pattern is legacy debt (see Aave v3.6) and is being eliminated — see Aave_Vendored_Dependencies_Dedup_Plan.md and DEDUPLICATION.md.
Step-by-step port workflow
0. Gate checks (before any copy)
- License — record SPDX/license. BUSL and similar need clearance before vendoring (
DEFI_PORTING_PRD.mdA.6). - Upstream pin — exact git tag or commit hash.
- Live deployments — chain + addresses for fork tests (official docs/explorer).
- Dependency inventory — list every import path the protocol uses; classify each as:
- already in
contracts/external/... - missing → must expand external first
- protocol-unique → stays with domain code
- already in
- Vyper / multi-pragma — decide interfaces+fork vs full
.vyvendoring (DEFI_PORTING_PRD.mdA.6).
1. Expand shared external/ first
Rule: expand first, migrate second, delete last.
- If the protocol needs OZ
AccessManagerand it is missing fromexternal/openzeppelin-contracts/, add the file(s) to external (matching the version the protocol expects) before rewriting imports. - Prefer the exact version/text the protocol compiles against when behavior depends on it.
- Preserve existing Crane adaptations already in external (e.g. SafeERC20 routing to Crane
IERC20where intentional).
Semantic routing (critical — learned from dedup failures):
| Consumer needs | Import target |
|---|---|
OZ semantics (Context, _msgSender, string reverts, upgradeable bases, AccessManager, classic Ownable) |
@crane/contracts/external/openzeppelin-contracts... (correct major) |
| Crane-native Diamond code | @crane/contracts/access/..., @crane/contracts/utils/... |
| Never | Silently swap OZ-semantic ports onto Crane-native Ownable/Context during a "dedup" |
Crane-native and OZ-vendored libraries are not interchangeable even when names match.
2. Vendor protocol domain sources
- Copy protocol contracts into
contracts/external/<protocol>/(orcontracts/protocols/...only when the tree is already a Crane-shaped port — prefer external for raw upstream). - No new git submodules — sources are copied in.
- Drop non-runtime bloat when safe:
certora/, upstreamtest/,docs/,scripts/,audits/(do not delete until confirmed unused by compile). - Write
contracts/external/<protocol>/VENDOR.md.
3. Remap all imports
Always use @crane/ absolute imports for new/edited code:
// ✓ Correct
import {SafeCast} from "@crane/contracts/external/openzeppelin-contracts/utils/math/SafeCast.sol";
import {IERC20} from "@crane/contracts/interfaces/IERC20.sol"; // when Crane-canonical
// ✗ Forbidden for new work
import {SafeCast} from "../../../dependencies/openzeppelin/contracts/utils/math/SafeCast.sol";
import {SafeCast} from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; // bare alias dump
Bulk rewrite tips (from DEDUPLICATION.md):
- Suffix-based rewrite of path fragments (works for both
@crane/and relative forms). - Grep both
@crane/...absolute paths and relative.//../forms before deleting any file. - Grep inside the vendored tree for internal consumers before deleting OZ interface files.
- After large moves:
rm -rf cache outthenforge build.
4. Crane wrapper surface
Minimum for a usable port (see DEFI_PORTING_PRD.md A.5):
| Piece | When |
|---|---|
Interfaces under contracts/interfaces/ or protocols/.../interfaces/ |
Always for consumer-facing API |
*Service.sol |
Always — stateless helpers for Diamond/strategy code |
*AwareRepo / Facet-Target-Repo |
When state lives in a Diamond |
*DFPkg |
When deployable as a Diamond package |
Protocol skill under .claude/skills/ |
After port stabilizes (writing-skills) |
Follow crane-architecture + crane-code-style + crane-natspec for wrappers. Domain contracts keep upstream NatSpec; wrappers get full Crane NatSpec + tags.
5. Tests and verification
Mandatory. Load crane-porting-verification and implement the full gate set. A port without tests is incomplete.
6. Docs and skills
- Update protocol section of
docs/CODEBASE_MAP.md(or regenerate). - Cross-link
docs/protocols/if applicable. - Add/update protocol skill(s) so future agents reuse the port.
VENDOR.md template
Place at contracts/external/<name>/VENDOR.md:
# <name> vendor
| Item | Value |
|------|-------|
| Upstream | org/repo |
| Pin | `<git tag or commit>` |
| Solidity files (this tree) | N |
| Copy date | YYYY-MM-DD |
| License | SPDX / note clearance if BUSL |
| Import policy | Shared OZ/Solady remapped to `@crane/contracts/external/...`; unique transitives under `contracts/external/<dep>/` |
## Adaptations
- Imports rewritten from upstream remappings to `@crane/...` paths (no new Foundry alias paths).
- Exact `pragma solidity =X.Y.Z` relaxed only where required for multi-version Foundry compile (document each).
- List any intentional Crane hooks (e.g. IERC20 path).
## Inventory
Path to inventory file or bullet list of top-level modules included/excluded.
Reference example: contracts/external/lido/VENDOR.md.
Dependency substitution map (default)
| Upstream dependency | Crane target | Notes |
|---|---|---|
| OZ ERC20 / Ownable / AccessControl / SafeERC20 / … | external/openzeppelin-contracts/ (match major) |
OZ semantics only |
| OZ upgradeable | external/openzeppelin-upgradeable* |
Do not mix majors blindly |
| Solady | external/solady/ |
Expand external if symbol missing |
| Solmate | external/solmate/ |
Uni V4-style use is OK when intentional |
| Permit2 | protocols/utils/permit2/ + Aware layer |
Already ported |
| Chainlink / Pyth / RedStone | external/ + protocols/oracles/ |
Reuse adapters |
| EVC | Euler port under protocols/lending/euler/ |
Reuse, do not re-vendor |
| forge-std | project lib/forge-std / external stub policy |
Do not nest private forge-std |
Full program map: DEFI_PORTING_PRD.md Part B.
Import & remapping rules
- Primary form:
@crane/contracts/...only for Crane work. - Do not add new Foundry remapping aliases for each ported protocol.
- Do not edit
foundry.toml/remappings.txtunless explicitly required and approved. - Existing convenience remaps (e.g.
@openzeppelin/→ external) may be used when they already point at the canonical external tree — prefer@crane/contracts/external/...in new code for clarity. - Relative imports inside a freshly vendored tree should be converted to
@crane/during cleanup (at least for cross-package edges).
Anti-patterns (reject these)
- New
protocols/.../dependencies/openzeppelin*or.../dependencies/solady*trees. - Replacing OZ Ownable with Crane Solady Ownable "to dedupe" (behavior change).
- Bumping OZ major as part of a port without an explicit version decision + tests.
- Inventing interface mocks for the protocol SUT when stubs/ports or forks exist.
- Porting without fork addresses, VENDOR.md, or tests.
newfor production Crane facets/DFPkgs in tests (use factories); protocol ports in TestBases may usenewfor the ported protocol only.- Leaving dead relative imports after a move (grep both forms).
Definition of done (port complete)
A protocol port is done only when all hold:
- Domain sources under the correct
external/and/orprotocols/paths, pinned inVENDOR.md. - Shared deps remapped to
@crane/contracts/external/...(no private OZ/Solady clones for new work). - No new git submodules; no unauthorized remapping edits.
- Crane wrapper surface: interfaces + Service (minimum); Aware/FTR/DFPkg as designed.
- Verification gates from
crane-porting-verificationall green. - Docs/CODEBASE_MAP updated; protocol skill added or scheduled.
- Task/PRD section updated if part of
DEFI_PORTING_*program.
Key reference files
DEFI_PORTING_PRD.md
DEFI_PORTING_PRIORITIZATION.md
DEFI_PORTING_GAP_ANALYSIS.md
DEDUPLICATION.md
Aave_Vendored_Dependencies_Dedup_Plan.md
contracts/external/lido/VENDOR.md
contracts/StyleGuide.sol
AGENTS.md
docs/CODEBASE_MAP.md
docs/concepts/building-with-crane.md
Exemplar ports (study these)
| Protocol | Domain / external | Crane wrappers |
|---|---|---|
| Lido | external/lido/ + external/aragon/ |
protocols/staking/ethereum/lido/ |
| Uniswap V2 | external/uniswap/v2-* |
protocols/dexes/uniswap/v2/{services,aware,stubs,test} |
| Balancer V3 | external/balancer/ |
protocols/dexes/balancer/v3/ + diamond routers |
| Aave v4 | under protocols/lending/aave/v4/ (large) |
Services + full test tree under test/foundry/spec/protocols/lending/aave/v4/ |
| Aave v3.6 deps cleanup | still has legacy dependencies/ |
Migration target is external OZ — follow dedup plan |
See also
skill:crane-porting-verification— mandatory tests, fork parity, Behavior, DoD checklistskill:crane-testing— TestBase / Behavior / Handler patternsskill:crane-architecture— wrappers Facet-Target-Repo / DFPkgskill:crane-code-style— imports, headers, namingskill:crane-adversarial-testing— abuse suite for wrappers that hold valueskill:writing-skills— ship a protocol skill after the port- Agent:
crane-porter— specialized agent identity for this work