Skip to content
3F Docs

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.

Clone the repository and install dependencies:

Terminal window
git clone https://github.com/3FLabs/grunt.git
cd grunt
forge install

The 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.)

Compile all contracts:

Terminal window
forge build

Run the full test suite:

Terminal window
forge test

Run tests with verbose output for debugging:

Terminal window
forge test -vvv

Run a specific test file:

Terminal window
forge test --match-path test/facility/Facility.t.sol
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 libraries
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

All contracts use the beacon proxy pattern via factories. This has two important implications for development:

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:

grunt.storage.PositionManager
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.

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.

  • 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.