-
Notifications
You must be signed in to change notification settings - Fork 1
Add ST0xOrchestrator: per-token mint/burn proxy #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 cbcb158
Redesign ST0xOrchestrator as a singleton (meeting + David's review)
hardyjosh 10a1491
Burn walk: revert on insufficient receipts, never mint-on-demand
hardyjosh 841fb0b
Orchestrator review pass: interface, nonce replay, hook auto-lower, s…
hardyjosh c453710
withdrawReceipt: add nonReentrant for parity with sibling sweeps
hardyjosh 3a80dc7
fmt: match CI-pinned forge fmt output
hardyjosh ef6fb41
Zero slither findings: real fixes where possible, justified disables …
hardyjosh 471d405
tests: one contract per file (rainix-sol-single-contract)
hardyjosh 46eff22
Harden burn hook against zero-value griefing + fold in mutation-test …
ba1cf81
Drop @custom:oz-upgrades-unsafe-allow tag (no-custom-natspec CI check)
hardyjosh 47062c1
CHANGELOG: describe the shipped singleton orchestrator, not the redes…
hardyjosh 68aa14f
refactor(orchestrator): single Zoltu beacon-set deployer + fold in de…
thedavidmeister 32883de
docs(audit): add May 2026 st0x.deploy audit report
thedavidmeister 5d3d65c
refactor(orchestrator): give the beacon-set deployer a real ERC-165 i…
thedavidmeister 51b162e
refactor(orchestrator): move the Deployment event onto the deployer i…
thedavidmeister 9d8a94f
ci: assert every generated deploy pointer is pinned in LibProdDeployV4
thedavidmeister bd5a988
refactor(deploy): version deploy constants by st0x-deploy release tag
thedavidmeister 4f742d1
feat(deploy): pin per-release creation + runtime bytecode as frozen h…
thedavidmeister 4c598af
test(deploy): verify frozen per-release bytecode reproduces + matches…
thedavidmeister 7de9339
test(deploy): assert the deployed 0.1.2 orchestrator + deployer on-chain
thedavidmeister aff859b
Address #222 review + CodeRabbit threads
thedavidmeister File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
|
|
||
| /// @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; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.