Skip to content

v3.2.0

Latest

Choose a tag to compare

@wischli wischli released this 08 Jul 13:07
87c358f

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, and ApprovalGuard are used as bookend/tail commands inside scripts to enforce value, throughput, and approval invariants.
  • Cross-chain accounting: An ERC-6909 AccountingToken tracks 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 single execute() 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.