Getting Started
This guide walks you through cloning, building, and testing the Grunt smart contracts, and provides an overview of the repository structure. grunt is the name of the Solidity repository behind 3F’s solution for leveraging RWAs and asynchronous assets.
Prerequisites
Section titled “Prerequisites”- Foundry (forge, cast, anvil)
- Git
Repository Setup
Section titled “Repository Setup”Clone the repository and install dependencies:
git clone https://github.com/3FLabs/grunt.gitcd gruntforge installThe project depends on:
| Dependency | Purpose |
|---|---|
forge-std |
Foundry standard library for testing and scripting |
morpho-blue |
Morpho Blue lending protocol interfaces and libraries |
solady |
Gas-optimized Solidity utilities (OwnableRoles, ERC20, etc.) |
Building
Section titled “Building”Compile all contracts:
forge buildTesting
Section titled “Testing”Run the full test suite:
forge testRun tests with verbose output for debugging:
forge test -vvvRun a specific test file:
forge test --match-path test/facility/Facility.t.solContract Structure
Section titled “Contract Structure”src/├── facility/ # Core orchestration hub│ ├── Facility.sol # Main entry point combining all modules│ ├── IntentDescriptor.sol # ERC-6909 metadata provider│ └── base/ # Abstract base contracts│ ├── FacilityIntents.sol # Intent lifecycle management│ ├── FacilityLP.sol # Liquidity provider operations│ ├── FacilityFunds.sol # Fund order operations│ ├── FacilityRequests.sol # Request contract coordination│ ├── FacilityPositionManager.sol # Position manager integration│ ├── FacilitySwap.sol # Token swapping with guardian signatures│ └── FacilityRoles.sol # Access control definitions├── request/ # Dual-token PT/YT vault system│ ├── Request.sol # Main request contract│ ├── RequestFactory.sol # Beacon proxy factory│ ├── Vault.sol # ERC4626-style redemption vault│ └── abstract/ # Base contracts and token controllers├── manager/ # Multi-position aggregator│ ├── PositionManager.sol # Main position manager│ ├── PositionManagerFactory.sol│ ├── base/ # Admin, fees, shares, rebalancing│ └── rebalancer/ # Flash-loan based rebalancer├── funds/ # External asset wrappers│ ├── USCCFund.sol # Bitwise USCC integration│ ├── USCCFundFactory.sol│ └── WrappedAsset.sol # wUSCC wrapper token├── borrow/ # Lending protocol integrations│ ├── MorphoBorrowPosition.sol│ └── MorphoBorrowPositionFactory.sol├── guard/ # Compliance controls│ ├── TransferGuard.sol│ └── TransferGuardFactory.sol├── interfaces/ # All interface definitions└── libs/ # Shared librariesModule Summary
Section titled “Module Summary”| Directory | Description |
|---|---|
src/facility |
Central hub that orchestrates intents, deposits, fund operations, request settlement, position management, and token swaps |
src/request |
Dual-token (PT/YT) system for bridge facilitator bridge loan settlement |
src/manager |
Aggregates multiple borrow positions into a single ERC20 share token tracking net equity |
src/funds |
Standardized wrappers for asynchronous asset settlement (e.g., Bitwise USCC) |
src/borrow |
Modular adapters for collateralized debt positions on lending venues (e.g., Morpho Blue) |
src/guard |
Compliance controls enforcing blocklist/whitelist rules on token transfers |
src/interfaces |
Interface definitions shared across all modules |
src/libs |
Shared utility libraries |
Proxy Architecture
Section titled “Proxy Architecture”All contracts use the beacon proxy pattern via factories. This has two important implications for development:
ERC-7201 Namespaced Storage
Section titled “ERC-7201 Namespaced Storage”Every contract uses ERC-7201 namespaced storage to prevent storage collisions across proxy upgrades. Storage is accessed through a single struct located at a deterministic slot:
struct PositionManagerStorage { address collateralAsset; address debtAsset; address transferGuard; // ...}
function _getPositionManagerStorage() private pure returns (PositionManagerStorage storage $){ assembly { $.slot := POSITION_MANAGER_STORAGE_LOCATION }}When extending or modifying contracts, always add new fields to the end of the storage struct. Never reorder or remove existing fields.
Initialization Instead of Constructors
Section titled “Initialization Instead of Constructors”Since proxies delegate calls to a shared implementation, constructors cannot be used for per-instance setup. All contracts use initialize() functions instead:
function initialize( address collateralAsset, address debtAsset, address owner) external initializer { _initializeOwner(owner); PositionManagerStorage storage $ = _getPositionManagerStorage(); $.collateralAsset = collateralAsset; $.debtAsset = debtAsset;}The initializer modifier ensures each proxy instance can only be initialized once.
Next Steps
Section titled “Next Steps”- Deployment – Deploy contracts using the factory pattern and manage upgrades.
- Security – Understand the security mechanisms and invariants the protocol enforces.
- Roles – Complete reference of role-based access control across all contracts.