v3.2.0 delivers the Onchain Portfolio Manager (Onchain PM): a per-pool execution engine that lets asset managers run pre-approved, multi-step onchain strategies (supply, withdraw, swap, bridge) directly against a Centrifuge pool's balance sheet, under strict cryptographic authorization. Hub managers authorize what can run rather than approving individual transactions, and everything settles against the pool's existing balance sheet and accounting. It closes the loop on the full institutional asset lifecycle already live on Centrifuge (issuance, pricing, distribution) by adding active, onchain management on the same audited rails.
Onchain Portfolio Manager
The design separates policy from execution. A hub manager assigns each strategist a Merkle-root policy over the set of workflow scripts they are allowed to run. Strategists then execute any script in that tree, but nothing outside it. Governance can pin individual parameters (addresses, amounts) inside a script so a strategist cannot substitute them at execution time. Runtime guards bound slippage, dangling approvals, and cumulative throughput.
- Weiroll VM execution: Scripts are ordered weiroll command sequences where each step can consume the outputs of previous ones, so strategies react to live onchain state at execution time (e.g. read a live balance, then bridge it).
- Per-strategist Merkle policy: Only scripts committed to a strategist's Merkle root can execute. Policies are set from the Hub via cross-chain trusted calls.
- Pinned state (
stateBitmap): Governance-approved state slots are folded into the script hash. Strategists supply the rest but cannot alter pinned slots. - Composable guards:
SlippageGuard,CircuitBreakerGuard, andApprovalGuardare used as bookend/tail commands inside scripts to enforce value, throughput, and approval invariants. - Cross-chain accounting: An ERC-6909
AccountingTokentracks in-flight assets and cross-chain liabilities as onchain receipts, so NAV stays consistent while capital is in transit. - Flash-loan callbacks: Pre-committed callback scripts (e.g. Aave V3 flash loans via
FlashLoanHelper) execute within a singleexecute()without a separate proof.
Integration Points
Execute a strategy
function execute(
bytes32[] calldata commands, // weiroll command bytes
bytes[] calldata state, // weiroll state (<= 128 slots)
uint128 stateBitmap, // bit i set => state[i] is governance-pinned
Callback[] calldata callbacks, // pre-committed (hash, caller) callback commitments
bytes32[] calldata proof // Merkle proof for the script hash leaf
) external payable;The caller must be a strategist with a non-zero policy root. The script hash is recomputed onchain via computeScriptHash(...) and verified against policy[strategist].
Assign a policy (from the Hub)
Policies are updated through the ContractUpdater, which routes a cross-chain trusted call into the pool's Onchain PM:
// OnchainPM, invoked only by the ContractUpdater
function trustedCall(PoolId poolId, ShareClassId, bytes calldata payload) external;
// payload = abi.encode(bytes32 strategist, bytes32 newRoot)Guards (weiroll targets)
// SlippageGuard: bookend a script; net value change across touched assets must stay within bound
function open(...) external;
function close(...) external;
// CircuitBreakerGuard: rolling-window limits keyed per caller
function tally(bytes32 key, uint256 amount, uint256 max, uint256 window) external; // throughput cap
function delta(bytes32 key, uint256 currentValue, uint256 newValue, uint256 maxDeltaBps, uint256 window) external; // per-update deviation cap
// ApprovalGuard: tail command ensuring no dangling ERC20 approvals remain
function checkZeroAllowances(ApprovalEntry[] calldata entries) external view;Deterministic deployment
// OnchainPMFactory
function newOnchainPM(PoolId poolId) external returns (IOnchainPM); // CREATE2, salt = poolId
function getAddress(PoolId poolId) external view returns (address);New Contracts
Spoke-side
| Contract | Purpose |
|---|---|
OnchainPM
| Per-pool weiroll execution engine with Merkle-proof script authorization and a pinned-state bitmap. Compiled with via_ir (in src-ir/) for the VM's stack depth.
|
OnchainPMFactory
| Deploys pool-specific OnchainPM instances deterministically via CREATE2.
|
AccountingToken
| ERC-6909 multi-token for in-flight async requests and cross-chain liabilities; token IDs encode pool ID, asset, and a liability flag, shared across all pools. |
OnOffRamp
| Balance-sheet manager for ERC20 deposits/withdrawals with accounting-token receipts. Permissionless onramp, permissioned offramp. |
FlashLoanHelper
| Periphery bridging Aave V3 flash loans into OnchainPM.executeCallback().
|
ScriptHelpers
| Stateless utility target for weiroll scripts (conditions, arithmetic, bps, decimal conversion). |
SlippageGuard
| Bookend guard bounding net value change per script and cumulative loss per period. |
CircuitBreakerGuard
| Rolling-window guard capping cumulative throughput and per-update value deviation. |
ApprovalGuard
| Stateless guard asserting listed ERC20 approvals are zero. |
Libraries
| Library | Purpose |
|---|---|
MerkleProofLib
| Merkle proof verification for script authorization. |
TransientArrayLib
| Transient-storage arrays backing callback commitments. |
TransientStorageLib
| Typed transient-storage reads/writes for guard bookend state. |
Audits
The Onchain PM and its guards were audited by xmxanuel, Sherlock and BurraSec (April 2026). All reported findings are resolved in this release. See docs/audits/ on main branch for all reports. Please note the reports belonging to this release are missing on this tag to ensure the release commit hash is exactly the audited one.
Migration
No action required for existing pools. The OnchainPM is opt-in per pool: it is deployed on demand via OnchainPMFactory and activated only when a hub manager assigns a strategist policy. Pools that do not use it are unaffected.