Skip to content
3F Docs

Deployment

All major Grunt contracts are deployed through factory contracts that implement the beacon proxy pattern. This guide covers how factories work, how to deploy new instances, and how to manage upgrades.

Each factory owns an UpgradeableBeacon that points to a single implementation contract. When the factory creates a new instance, it deploys a lightweight BeaconProxy that delegates all calls to the implementation address stored in the beacon.

graph TD
    F[Factory] --> B[UpgradeableBeacon]
    B --> I["Implementation (shared)"]
    I -.->|delegates| P1["BeaconProxy (instance 1)"]
    I -.->|delegates| P2["BeaconProxy (instance 2)"]
    I -.->|delegates| PN["BeaconProxy (instance N)"]

This architecture provides two key properties:

  1. Gas-efficient deployment – each new instance only deploys a minimal proxy, not the full implementation bytecode.
  2. Atomic upgrades – updating the beacon’s implementation pointer upgrades every proxy in a single transaction.
Factory Creates Key Parameters
RequestFactory Request + PT vault + YT vault asset, funding/repayment deadlines, puller
PositionManagerFactory PositionManager instances collateral asset, debt asset, owner
MorphoBorrowPositionFactory MorphoBorrowPosition instances Morpho contract, market ID, position manager, custom LLTV
USCCFundFactory USCCFund wrappers USCC token, recipient, owner
TransferGuardFactory TransferGuard instances owner

Deploy a new Morpho Blue borrow position with a custom LLTV:

import {MorphoBorrowPositionFactory} from "src/borrow/MorphoBorrowPositionFactory.sol";
import {IMorpho} from "src/interfaces/IMorpho.sol";
// Factory is already deployed and owns its beacon
MorphoBorrowPositionFactory factory = MorphoBorrowPositionFactory(factoryAddress);
// Create a new borrow position with 72% custom LLTV
address bp = factory.createBorrowPosition(
morpho, // IMorpho -- Morpho Blue protocol contract
marketId, // Id -- Morpho market ID
positionManager, // address -- owner controlling this position
0.72e18 // uint256 -- custom LLTV (must be <= market LLTV)
);

The factory emits a BorrowPositionCreated event with the new proxy address and initialization parameters.

Deploy a new Position Manager for a collateral/debt pair:

import {PositionManagerFactory} from "src/manager/PositionManagerFactory.sol";
PositionManagerFactory factory = PositionManagerFactory(factoryAddress);
address pm = factory.createPositionManager(
collateralAsset, // address -- e.g., wUSCC
debtAsset, // address -- e.g., USDC
owner // address -- admin who can configure the PM
);

After deployment, the owner must configure the Position Manager by adding borrow modules and setting queues:

PositionManager pm = PositionManager(pmAddress);
// Add a borrow position as a module
pm.addModule(borrowPositionAddress);
// Set the supply queue (order matters for deposit routing)
PositionManager.SupplyQueueEntry[] memory supplyQueue =
new PositionManager.SupplyQueueEntry[](1);
supplyQueue[0] = PositionManager.SupplyQueueEntry({
position: borrowPositionAddress,
maxBorrow: 5_000_000e6 // max borrow per deposit
});
pm.setSupplyQueue(supplyQueue);
// Set the withdrawal queue
address[] memory withdrawalQueue = new address[](1);
withdrawalQueue[0] = borrowPositionAddress;
pm.setWithdrawalQueue(withdrawalQueue);

Deploy a Request with its associated PT and YT vaults:

import {RequestFactory} from "src/request/RequestFactory.sol";
RequestFactory factory = RequestFactory(factoryAddress);
address request = factory.createRequest(
asset, // address -- the funding asset (e.g., USDC)
fundingDeadline, // uint256 -- timestamp after which funding closes
repaymentDeadline, // uint256 -- timestamp after which redemptions open
puller // address -- the Facility address authorized to pull funds
);

The factory deploys three contracts atomically: the Request itself, a PT vault, and a YT vault.

Deploy a compliance guard:

import {TransferGuardFactory} from "src/guard/TransferGuardFactory.sol";
TransferGuardFactory factory = TransferGuardFactory(factoryAddress);
address guard = factory.createTransferGuard(
owner // address -- admin who configures token modes and address statuses
);

Deploy a fund wrapper for Bitwise USCC:

import {USCCFundFactory} from "src/funds/USCCFundFactory.sol";
USCCFundFactory factory = USCCFundFactory(factoryAddress);
address fund = factory.createUSCCFund(
uscc, // address -- USCC token contract
recipient, // address -- Bitwise deposit recipient
owner // address -- admin
);

The beacon owner can upgrade all proxies created by a factory by updating the beacon’s implementation pointer:

import {UpgradeableBeacon} from "solady/utils/UpgradeableBeacon.sol";
// Get the beacon address from the factory
address beacon = factory.beacon();
// Deploy a new implementation
address newImplementation = address(new MorphoBorrowPosition());
// Upgrade all proxies at once (only callable by beacon owner)
UpgradeableBeacon(beacon).upgradeTo(newImplementation);

After this call, every BeaconProxy created by the factory delegates to the new implementation. Existing storage is preserved – the proxy’s storage layout is independent of the implementation bytecode.

Important considerations when upgrading:

  • New implementations must be storage-compatible with the previous version (append-only to ERC-7201 storage structs)
  • The beacon owner should be a multisig or timelock for production deployments
  • Test the new implementation thoroughly against existing proxy state before upgrading

The repository includes Foundry deployment scripts in the script/ directory. The primary deployment script is script/Request.s.sol, which demonstrates deploying the full Request stack.

Run a deployment script:

Terminal window
forge script script/Request.s.sol --rpc-url $RPC_URL --broadcast

For local development, use Anvil:

Terminal window
# Start a local node
anvil
# Deploy to local node
forge script script/Request.s.sol --rpc-url http://localhost:8545 --broadcast

Since proxies delegate to shared implementations, you only need to verify the implementation contracts and the factories. The proxy bytecode is standard BeaconProxy from Solady.

Terminal window
forge verify-contract \
$IMPLEMENTATION_ADDRESS \
src/borrow/MorphoBorrowPosition.sol:MorphoBorrowPosition \
--chain-id 1 \
--etherscan-api-key $ETHERSCAN_KEY