Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2d17b6f
Add ST0xOrchestrator: per-token mint/burn proxy
hardyjosh Jul 1, 2026
cbcb158
Redesign ST0xOrchestrator as a singleton (meeting + David's review)
hardyjosh Jul 2, 2026
10a1491
Burn walk: revert on insufficient receipts, never mint-on-demand
hardyjosh Jul 3, 2026
841fb0b
Orchestrator review pass: interface, nonce replay, hook auto-lower, s…
hardyjosh Jul 3, 2026
c453710
withdrawReceipt: add nonReentrant for parity with sibling sweeps
hardyjosh Jul 3, 2026
3a80dc7
fmt: match CI-pinned forge fmt output
hardyjosh Jul 3, 2026
ef6fb41
Zero slither findings: real fixes where possible, justified disables …
hardyjosh Jul 3, 2026
471d405
tests: one contract per file (rainix-sol-single-contract)
hardyjosh Jul 3, 2026
46eff22
Harden burn hook against zero-value griefing + fold in mutation-test …
Jul 3, 2026
ba1cf81
Drop @custom:oz-upgrades-unsafe-allow tag (no-custom-natspec CI check)
hardyjosh Jul 6, 2026
47062c1
CHANGELOG: describe the shipped singleton orchestrator, not the redes…
hardyjosh Jul 6, 2026
68aa14f
refactor(orchestrator): single Zoltu beacon-set deployer + fold in de…
thedavidmeister Jul 7, 2026
32883de
docs(audit): add May 2026 st0x.deploy audit report
thedavidmeister Jul 7, 2026
5d3d65c
refactor(orchestrator): give the beacon-set deployer a real ERC-165 i…
thedavidmeister Jul 7, 2026
51b162e
refactor(orchestrator): move the Deployment event onto the deployer i…
thedavidmeister Jul 7, 2026
9d8a94f
ci: assert every generated deploy pointer is pinned in LibProdDeployV4
thedavidmeister Jul 7, 2026
bd5a988
refactor(deploy): version deploy constants by st0x-deploy release tag
thedavidmeister Jul 7, 2026
4f742d1
feat(deploy): pin per-release creation + runtime bytecode as frozen h…
thedavidmeister Jul 7, 2026
4c598af
test(deploy): verify frozen per-release bytecode reproduces + matches…
thedavidmeister Jul 7, 2026
7de9339
test(deploy): assert the deployed 0.1.2 orchestrator + deployer on-chain
thedavidmeister Jul 7, 2026
aff859b
Address #222 review + CodeRabbit threads
thedavidmeister Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## V4 (rain.vats 0.1.6)

### New contracts

- **ST0xOrchestrator**: Per-token mint/burn proxy (Initializable, deployed as
`BeaconProxy` clones). Holds the vault's `DEPOSIT` + `WITHDRAW` roles and
owns every ERC-1155 receipt; permissioned callers (`MINT_BURN_ROLE`) mint
and burn without touching receipts. Burn walks a sequential receipt-id
pointer with a mint-on-demand fallback (observable via
`BurnShortfallMinted`) for burning more than was ever minted. Admin escape
hatches: `setBurnIndex`, `withdrawReceipt`, `withdrawShares`.
- **ST0xOrchestratorBeaconSetDeployer**: Beacon-set deployer minting per-token
orchestrator clones behind a shared `UpgradeableBeacon`. Config-in-
constructor base; the Zoltu-deployable concrete subclass with hardcoded
`LibProdDeploy*` config lands with the deploy wiring.

## V3 (corporate actions)

V3 introduces the corporate-actions diamond facet and wires it into the receipt
Expand Down
480 changes: 480 additions & 0 deletions src/concrete/ST0xOrchestrator.sol

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions src/concrete/deploy/ST0xOrchestratorBeaconSetDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2026 S01 Issuer GmbH
pragma solidity =0.8.25;

import {IBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/IBeacon.sol";
import {UpgradeableBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/UpgradeableBeacon.sol";
import {BeaconProxy} from "@openzeppelin-contracts-5.6.1/proxy/beacon/BeaconProxy.sol";
import {IERC165} from "@openzeppelin-contracts-5.6.1/utils/introspection/IERC165.sol";

import {ST0xOrchestrator} from "../ST0xOrchestrator.sol";

/// Thrown when the deployer is constructed with `initialOwner == address(0)`.
error ZeroInitialOwner();

/// Thrown when the deployer is constructed with a zero implementation
/// address for the orchestrator.
error ZeroOrchestratorImplementation();

/// Thrown when `deploy` is called with `owner == address(0)`.
error ZeroOwner();

/// Configuration for `ST0xOrchestratorBeaconSetDeployer` construction.
/// @param initialOwner Owner of the internal `UpgradeableBeacon`. In
/// production this is the owner multisig.
/// @param initialOrchestratorImplementation Implementation contract the
/// beacon initially points at.
struct ST0xOrchestratorBeaconSetDeployerConfig {
address initialOwner;
address initialOrchestratorImplementation;
}

/// @title ST0xOrchestratorBeaconSetDeployer
/// @notice Deploys `ST0xOrchestrator` singletons as `BeaconProxy` instances
/// pointing at a shared beacon owned by the initial owner multisig. The
/// orchestrator is a singleton — one instance serves every token — so a
/// deployment is not bound to any vault. The beacon is retained so a single
/// upgrade rolls the deployed orchestrator(s) forward at once, and so a
/// future distinct token set could be served by a separate orchestrator on
/// its own beacon; for now exactly one is deployed.
///
/// This base contract takes its config as a constructor parameter (for
/// testability and reuse) and is therefore NOT Zoltu-deployable itself. A
/// concrete production subclass hardcodes the config via `LibProdDeploy*`
/// constants so its constructor takes no dynamic input — mirroring the
/// `OffchainAssetReceiptVaultBeaconSetDeployer` /
/// `StoxOffchainAssetReceiptVaultBeaconSetDeployer` pattern.
contract ST0xOrchestratorBeaconSetDeployer is IERC165 {
/// Emitted when an `ST0xOrchestrator` singleton is deployed.
/// @dev `deploy` is permissionless, so anyone can emit this event with
/// any owner. Consumers MUST filter on the expected `owner` (and ideally
/// take the address from their own deploy transaction rather than event
/// discovery) — an attacker can front-run a lookalike that differs only
/// in who holds `DEFAULT_ADMIN_ROLE`.
/// @param sender The address that called `deploy`.
/// @param orchestrator Address of the newly deployed orchestrator proxy.
/// @param owner The address granted `DEFAULT_ADMIN_ROLE`.
event Deployment(address indexed sender, address indexed orchestrator, address owner);

/// The beacon every deployed orchestrator proxy points at.
IBeacon public immutable iOrchestratorBeacon;

constructor(ST0xOrchestratorBeaconSetDeployerConfig memory config) {
if (config.initialOwner == address(0)) revert ZeroInitialOwner();
if (config.initialOrchestratorImplementation == address(0)) revert ZeroOrchestratorImplementation();

iOrchestratorBeacon = new UpgradeableBeacon(config.initialOrchestratorImplementation, config.initialOwner);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/// @notice Deploy an `ST0xOrchestrator` singleton owned by `owner`.
/// Callable by anyone — no auth on the deployer; the deployed instance's
/// own `DEFAULT_ADMIN_ROLE` (held by `owner`) governs it.
function deploy(address owner) external returns (address) {
if (owner == address(0)) revert ZeroOwner();

bytes memory initData = abi.encodeCall(ST0xOrchestrator.initialize, (owner));
BeaconProxy proxy = new BeaconProxy(address(iOrchestratorBeacon), initData);

emit Deployment(msg.sender, address(proxy), owner);
return address(proxy);
}

/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
23 changes: 23 additions & 0 deletions src/interface/IMintRecipient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2026 S01 Issuer GmbH
pragma solidity ^0.8.25;

/// @title IMintRecipient
/// @notice Callback interface a contract recipient implements to authorise an
/// orchestrator mint of shares to itself, as an alternative to providing an
/// EIP-712 signature. Modelled on the ERC-1155 receiver acceptance pattern:
/// the orchestrator calls `authorizeMint` with the mint's canonical digest
/// and only proceeds if the recipient returns the function selector.
///
/// This lets a contract that cannot hold a private key (e.g. the atomic
/// bridge) gate mints on its own on-chain intent — it records the expected
/// mint, then returns the selector only for a digest it is expecting.
interface IMintRecipient {
/// @notice Authorise a mint of shares to this contract.
/// @param digest The orchestrator's EIP-712 digest binding
/// `(token, recipient, amount, nonce)`. The recipient should verify it
/// matches an intent it recorded and has not already consumed.
/// @return The `authorizeMint.selector` magic value if authorised; any
/// other value (or a revert) rejects the mint.
function authorizeMint(bytes32 digest) external returns (bytes4);
}
16 changes: 16 additions & 0 deletions src/interface/IST0xVaultBeaconSet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2026 S01 Issuer GmbH
pragma solidity ^0.8.25;

import {IBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/IBeacon.sol";

/// @title IST0xVaultBeaconSet
/// @notice The subset of `OffchainAssetReceiptVaultBeaconSetDeployer` the
/// orchestrator reads to enforce its vault-logic version lock. Every
/// production receipt vault + receipt is a `BeaconProxy` of these two
/// beacons, so reading the beacons' current `implementation()` is a single
/// global check that covers every token the orchestrator can touch.
interface IST0xVaultBeaconSet {
function iOffchainAssetReceiptVaultBeacon() external view returns (IBeacon);
function iReceiptBeacon() external view returns (IBeacon);
}
Loading
Loading