Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 52 additions & 0 deletions .github/workflows/manual-sol-artifacts-ethereum-0-1-1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Manual sol artifacts (Ethereum 0.1.1)
on:
workflow_dispatch:
inputs:
suite:
description: 'Suite to deploy (one contract per suite, deploy in listed order)'
required: true
type: choice
options:
# ===================================================================
# The audited 0.1.1 production set, shipped to Ethereum mainnet only
# via script/DeployProdV4_0_1_1.sol (stored 0.1.1 creation bytecode).
# The orchestrator (introduced at 0.1.2) is not part of this set.
#
# Deploy in the listed order; later entries reference earlier ones via
# dependency pointers so an out-of-order run trips the dep-codehash
# check.
# ===================================================================
# 1. No on-chain dependencies, plus the wrapped-token-vault chain
# (vault -> beacon -> beacon-set deployer) and the two authorizers.
- stox-receipt
- stox-wrapped-token-vault
- stox-wrapped-token-vault-beacon
- stox-wrapped-token-vault-beacon-set-deployer
- stox-offchain-asset-receipt-vault-authorizer-v1
- stox-offchain-asset-receipt-vault-payment-mint-authorizer-v1
# 2. Corporate-actions facet (no on-chain dependencies).
- stox-corporate-actions-facet
# 3. Receipt vault (depends on the corporate-actions facet).
- stox-receipt-vault
# 4. OARV beacon-set deployer (depends on the receipt and receipt-vault
# impls above).
- stox-offchain-asset-receipt-vault-beacon-set-deployer
# 5. Unified deployer (depends on the two set-deployers above).
- stox-unified-deployer
jobs:
deploy:
uses: rainlanguage/rainix/.github/workflows/rainix-manual-sol-artifacts.yaml@main
with:
suite: ${{ inputs.suite }}
# Ship the stored 0.1.1 creation bytecode, not current source.
script: script/DeployProdV4_0_1_1.sol:Deploy
# The 0.1.1 bytecode of the contracts that changed by 0.1.3 does not match
# current source, so Etherscan verification against current source would
# fail the run. Deploy unverified; verify manually from the 0.1.1 tag.
verify: false
# This script targets Ethereum only and runs unverified, so it needs only the
# deployer key and the Ethereum RPC. The reusable declares every other secret
# as optional and falls back to `|| vars.* || ''` for any it isn't given.
secrets:
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
RPC_URL_ETHEREUM_FORK: ${{ secrets.RPC_URL_ETHEREUM_FORK }}
2 changes: 2 additions & 0 deletions .github/workflows/manual-sol-artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ jobs:
RPC_URL_ARBITRUM_FORK: ${{ secrets.RPC_URL_ARBITRUM_FORK }}
RPC_URL_BASE_FORK: ${{ secrets.RPC_URL_BASE_FORK }}
RPC_URL_BASE_SEPOLIA_FORK: ${{ secrets.RPC_URL_BASE_SEPOLIA_FORK }}
RPC_URL_ETHEREUM_FORK: ${{ secrets.RPC_URL_ETHEREUM_FORK }}
RPC_URL_FLARE_FORK: ${{ secrets.RPC_URL_FLARE_FORK }}
RPC_URL_POLYGON_FORK: ${{ secrets.RPC_URL_POLYGON_FORK }}
CI_DEPLOY_BASE_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_BASE_ETHERSCAN_API_KEY }}
CI_DEPLOY_ETHEREUM_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_ETHEREUM_ETHERSCAN_API_KEY }}
1 change: 1 addition & 0 deletions .github/workflows/rainix-sol.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ jobs:
RPC_URL_ARBITRUM_FORK: ${{ secrets.RPC_URL_ARBITRUM_FORK }}
RPC_URL_BASE_FORK: ${{ secrets.RPC_URL_BASE_FORK }}
RPC_URL_BASE_SEPOLIA_FORK: ${{ secrets.RPC_URL_BASE_SEPOLIA_FORK }}
RPC_URL_ETHEREUM_FORK: ${{ secrets.RPC_URL_ETHEREUM_FORK }}
RPC_URL_FLARE_FORK: ${{ secrets.RPC_URL_FLARE_FORK }}
RPC_URL_POLYGON_FORK: ${{ secrets.RPC_URL_POLYGON_FORK }}
12 changes: 8 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,12 @@ library. When making changes to contract source:

`script/Deploy.sol` dispatches based on `DEPLOYMENT_SUITE` env var. One contract
per suite to avoid Zoltu factory nonce issues. Every suite targets the
`LibProdDeployV4` (rain.vats 0.1.6) pins and deploys to Base; the frozen pre-V4
deployments live in `LibProdDeployV1` / `LibProdDeployV2` as an audit trail and
are not redeployable from the current source.
`LibProdDeployV4` (rain.vats 0.1.6) pins and broadcasts to every network in
`LibStoxDeployNetworks.supportedNetworks()` (Base + Ethereum mainnet) — the
Zoltu deploy is idempotent per network, so a suite run skips networks that
already carry the artifact and deploys the identical bytecode to the ones that
don't. The frozen V1/V2 deployments in `LibProdDeployV1` / `LibProdDeployV2` are
an audit trail and are not redeployable from the current source.

- `stox-receipt-v4` — deploys StoxReceipt
- `stox-receipt-vault-v4` — deploys StoxReceiptVault
Expand All @@ -308,7 +311,8 @@ are not redeployable from the current source.
- `stox-corporate-actions-facet-v4` — deploys StoxCorporateActionsFacet

Manual deployment runs via the GitHub Actions workflow
(`manual-sol-artifacts.yaml`), which deploys to Base.
(`manual-sol-artifacts.yaml`), which broadcasts each suite to every supported
network in one run.

## Naming Conventions

Expand Down
5 changes: 5 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,17 @@ recursive_deps = false
arbitrum = "${ARBITRUM_RPC_URL}"
base = "${BASE_RPC_URL}"
base_sepolia = "${BASE_SEPOLIA_RPC_URL}"
ethereum = "${ETHEREUM_RPC_URL}"
flare = "${FLARE_RPC_URL}"
polygon = "${POLYGON_RPC_URL}"

[etherscan]
arbitrum = { key = "${CI_DEPLOY_ARBITRUM_ETHERSCAN_API_KEY}" }
base = { key = "${CI_DEPLOY_BASE_ETHERSCAN_API_KEY}" }
base_sepolia = { key = "${CI_DEPLOY_BASE_SEPOLIA_ETHERSCAN_API_KEY}" }
# `ethereum` is not a built-in foundry chain alias (foundry's canonical name for
# chain 1 is `mainnet`), so the chain id is pinned explicitly. Without it,
# verification fails with "unknown alias `ethereum`".
ethereum = { key = "${CI_DEPLOY_ETHEREUM_ETHERSCAN_API_KEY}", chain = 1 }
flare = { key = "${CI_DEPLOY_FLARE_ETHERSCAN_API_KEY}" }
polygon = { key = "${CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY}" }
212 changes: 212 additions & 0 deletions script/DeployProdV4_0_1_1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Script, console2} from "forge-std-1.16.1/src/Script.sol";

import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol";
import {LibProdDeployV4} from "../src/generated/LibProdDeployV4.sol";
import {LibStoxDeployNetworks} from "../src/lib/LibStoxDeployNetworks.sol";

/// @dev Error thrown when the DEPLOYMENT_SUITE env var does not match any known
/// suite.
error UnknownDeploymentSuite(bytes32 suite);

// One suite per contract to avoid Zoltu factory nonce issues.
//
// This script ships the audited 0.1.1 production set to Ethereum mainnet only.
// Unlike `script/Deploy.sol`, which deploys the CURRENT source (the 0.1.3 pins)
// to `LibStoxDeployNetworks.supportedNetworks()`, each suite here deploys the
// stored `LibProdDeployV4.*_CREATION_CODE_0_1_1` bytecode — the exact bytes the
// 0.1.1 audit covers — and asserts against the `_0_1_1` address/codehash pins.
// Deploying stored creation code (not `type(T).creationCode`) reproduces the
// audited 0.1.1 deployment regardless of what the current source compiles to.
//
// The orchestrator is intentionally absent: `ST0xOrchestrator` and its
// beacon-set deployer were introduced at 0.1.2, so they are not part of the
// 0.1.1 set.

bytes32 constant DEPLOYMENT_SUITE_STOX_RECEIPT = keccak256("stox-receipt");
bytes32 constant DEPLOYMENT_SUITE_STOX_RECEIPT_VAULT = keccak256("stox-receipt-vault");
bytes32 constant DEPLOYMENT_SUITE_STOX_WRAPPED_TOKEN_VAULT = keccak256("stox-wrapped-token-vault");
bytes32 constant DEPLOYMENT_SUITE_STOX_WRAPPED_TOKEN_VAULT_BEACON = keccak256("stox-wrapped-token-vault-beacon");
bytes32 constant DEPLOYMENT_SUITE_STOX_WRAPPED_TOKEN_VAULT_BEACON_SET_DEPLOYER =
keccak256("stox-wrapped-token-vault-beacon-set-deployer");
bytes32 constant DEPLOYMENT_SUITE_STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_BEACON_SET_DEPLOYER =
keccak256("stox-offchain-asset-receipt-vault-beacon-set-deployer");
bytes32 constant DEPLOYMENT_SUITE_STOX_UNIFIED_DEPLOYER = keccak256("stox-unified-deployer");
bytes32 constant DEPLOYMENT_SUITE_STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1 =
keccak256("stox-offchain-asset-receipt-vault-authorizer-v1");
bytes32 constant DEPLOYMENT_SUITE_STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_PAYMENT_MINT_AUTHORIZER_V1 =
keccak256("stox-offchain-asset-receipt-vault-payment-mint-authorizer-v1");
bytes32 constant DEPLOYMENT_SUITE_STOX_CORPORATE_ACTIONS_FACET = keccak256("stox-corporate-actions-facet");

contract Deploy is Script {
/// @dev Broadcasts a single contract via the Zoltu deterministic deployer on
/// Ethereum mainnet. Reads `DEPLOYMENT_KEY` from the environment, logs
/// diagnostic information (expected address, codehash, dependency state),
/// then delegates to `LibRainDeploy.deployAndBroadcast`.
/// @param creationCode The creation bytecode of the contract to deploy.
/// @param contractPath Fully qualified contract path
/// (e.g. "src/concrete/StoxReceipt.sol:StoxReceipt").
/// @param expectedAddress The deterministic address the contract must deploy
/// to.
/// @param expectedCodeHash The expected codehash of the deployed runtime
/// bytecode.
/// @param dependencies Addresses of contracts that must already be deployed
/// on Ethereum before this contract is deployed.
function deploySuite(
bytes memory creationCode,
string memory contractPath,
address expectedAddress,
bytes32 expectedCodeHash,
address[] memory dependencies
) internal {
string[] memory networks = new string[](1);
networks[0] = LibStoxDeployNetworks.ETHEREUM;
uint256 deployerPrivateKey = vm.envUint("DEPLOYMENT_KEY");

console2.log("Suite deploying (0.1.1):", contractPath);
console2.log("Expected address:", expectedAddress);
console2.log("Expected codehash:");
console2.logBytes32(expectedCodeHash);
console2.log("Chain ID:", block.chainid);
console2.log("Block number:", block.number);
console2.log("Dependencies count:", dependencies.length);
for (uint256 i = 0; i < dependencies.length; i++) {
console2.log(" Dep address:", dependencies[i]);
console2.log(" Dep code length:", dependencies[i].code.length);
console2.log(" Dep codehash:");
console2.logBytes32(dependencies[i].codehash);
}

LibRainDeploy.deployAndBroadcast(
vm,
networks,
deployerPrivateKey,
creationCode,
contractPath,
expectedAddress,
expectedCodeHash,
dependencies
);
}

/// @notice Entry point for the 0.1.1 Ethereum deployment script.
/// @dev Requires env vars:
/// - `DEPLOYMENT_KEY`: private key for the deployer account.
/// - `DEPLOYMENT_SUITE`: which contract to deploy (e.g. "stox-receipt").
/// One contract per run.
function run() public {
bytes32 suite = keccak256(bytes(vm.envString("DEPLOYMENT_SUITE")));
address[] memory noDeps = new address[](0);

if (suite == DEPLOYMENT_SUITE_STOX_RECEIPT) {
deploySuite(
LibProdDeployV4.STOX_RECEIPT_CREATION_CODE_0_1_1,
"src/concrete/StoxReceipt.sol:StoxReceipt",
LibProdDeployV4.STOX_RECEIPT_0_1_1,
LibProdDeployV4.STOX_RECEIPT_CODEHASH_0_1_1,
noDeps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_RECEIPT_VAULT) {
// StoxReceiptVault impl. Its `fallback()` delegatecalls the
// hardcoded corporate-actions facet, and a delegatecall to a
// code-less address silently no-ops — so the facet must already be
// on-chain. Declared as a dependency so LibRainDeploy reverts
// MissingDependency if the facet is not yet deployed on Ethereum.
address[] memory deps = new address[](1);
deps[0] = LibProdDeployV4.STOX_CORPORATE_ACTIONS_FACET_0_1_1;
deploySuite(
LibProdDeployV4.STOX_RECEIPT_VAULT_CREATION_CODE_0_1_1,
"src/concrete/StoxReceiptVault.sol:StoxReceiptVault",
LibProdDeployV4.STOX_RECEIPT_VAULT_0_1_1,
LibProdDeployV4.STOX_RECEIPT_VAULT_CODEHASH_0_1_1,
deps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_WRAPPED_TOKEN_VAULT) {
deploySuite(
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_CREATION_CODE_0_1_1,
"src/concrete/StoxWrappedTokenVault.sol:StoxWrappedTokenVault",
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_0_1_1,
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_CODEHASH_0_1_1,
noDeps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_WRAPPED_TOKEN_VAULT_BEACON) {
address[] memory deps = new address[](1);
deps[0] = LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_0_1_1;
deploySuite(
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_CREATION_CODE_0_1_1,
"src/concrete/StoxWrappedTokenVaultBeacon.sol:StoxWrappedTokenVaultBeacon",
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_0_1_1,
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_CODEHASH_0_1_1,
deps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_WRAPPED_TOKEN_VAULT_BEACON_SET_DEPLOYER) {
address[] memory deps = new address[](1);
deps[0] = LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_0_1_1;
deploySuite(
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_SET_DEPLOYER_CREATION_CODE_0_1_1,
"src/concrete/deploy/StoxWrappedTokenVaultBeaconSetDeployer.sol:StoxWrappedTokenVaultBeaconSetDeployer",
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_SET_DEPLOYER_0_1_1,
LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_SET_DEPLOYER_CODEHASH_0_1_1,
deps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_BEACON_SET_DEPLOYER) {
// Its constructor bakes beacons over the StoxReceipt and
// StoxReceiptVault impls, both of which must already have code.
address[] memory deps = new address[](2);
deps[0] = LibProdDeployV4.STOX_RECEIPT_0_1_1;
deps[1] = LibProdDeployV4.STOX_RECEIPT_VAULT_0_1_1;
deploySuite(
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_BEACON_SET_DEPLOYER_CREATION_CODE_0_1_1,
"src/concrete/deploy/StoxOffchainAssetReceiptVaultBeaconSetDeployer.sol:StoxOffchainAssetReceiptVaultBeaconSetDeployer",
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_BEACON_SET_DEPLOYER_0_1_1,
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_BEACON_SET_DEPLOYER_CODEHASH_0_1_1,
deps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_UNIFIED_DEPLOYER) {
// Embeds the OARV beacon-set deployer and the wrapped-token-vault
// beacon-set deployer it drives; both must already have code.
address[] memory deps = new address[](2);
deps[0] = LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_BEACON_SET_DEPLOYER_0_1_1;
deps[1] = LibProdDeployV4.STOX_WRAPPED_TOKEN_VAULT_BEACON_SET_DEPLOYER_0_1_1;
deploySuite(
LibProdDeployV4.STOX_UNIFIED_DEPLOYER_CREATION_CODE_0_1_1,
"src/concrete/deploy/StoxUnifiedDeployer.sol:StoxUnifiedDeployer",
LibProdDeployV4.STOX_UNIFIED_DEPLOYER_0_1_1,
LibProdDeployV4.STOX_UNIFIED_DEPLOYER_CODEHASH_0_1_1,
deps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1) {
deploySuite(
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_CREATION_CODE_0_1_1,
"src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol:StoxOffchainAssetReceiptVaultAuthorizerV1",
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1,
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_CODEHASH_0_1_1,
noDeps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_PAYMENT_MINT_AUTHORIZER_V1) {
deploySuite(
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_PAYMENT_MINT_AUTHORIZER_V1_CREATION_CODE_0_1_1,
"src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol:StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1",
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_PAYMENT_MINT_AUTHORIZER_V1_0_1_1,
LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_PAYMENT_MINT_AUTHORIZER_V1_CODEHASH_0_1_1,
noDeps
);
} else if (suite == DEPLOYMENT_SUITE_STOX_CORPORATE_ACTIONS_FACET) {
// StoxCorporateActionsFacet impl. No on-chain dependencies (the
// receipt-vault impl hardcodes its address but does not link to it
// at deploy time).
deploySuite(
LibProdDeployV4.STOX_CORPORATE_ACTIONS_FACET_CREATION_CODE_0_1_1,
"src/concrete/StoxCorporateActionsFacet.sol:StoxCorporateActionsFacet",
LibProdDeployV4.STOX_CORPORATE_ACTIONS_FACET_0_1_1,
LibProdDeployV4.STOX_CORPORATE_ACTIONS_FACET_CODEHASH_0_1_1,
noDeps
);
} else {
revert UnknownDeploymentSuite(suite);
}
}
}
21 changes: 18 additions & 3 deletions src/lib/LibStoxDeployNetworks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,27 @@ import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol";
/// @notice Single source of truth for the networks the ST0x production deploy
/// broadcasts to. `script/Deploy.sol` reads this instead of hardcoding the list
/// inline, so the supported-network set lives in one place. ST0x deploys to
/// Base only.
/// Base and Ethereum mainnet.
///
/// `LibRainDeploy.deployToNetworks` is idempotent per network — an
/// already-deployed contract is skipped and its codehash re-verified — so a
/// single suite run keeps every network in this list bytecode-identical by
/// construction.
library LibStoxDeployNetworks {
/// @notice Ethereum mainnet network name, matching the `[rpc_endpoints]`
/// alias in `foundry.toml` (resolved from `ETHEREUM_RPC_URL`), the same
/// pattern as every `LibRainDeploy` network constant.
/// @dev Declared here because `rain-deploy-0.1.4`'s `LibRainDeploy` has no
/// `ETHEREUM` constant. The Zoltu factory is deployed on Ethereum mainnet at
/// the canonical `LibRainDeploy.ZOLTU_FACTORY` address, so deterministic
/// deploys work unchanged.
string internal constant ETHEREUM = "ethereum";

/// @notice The networks each suite in `script/Deploy.sol` is broadcast to.
/// @return networks The list of network names (Base only).
/// @return networks The list of network names (Base + Ethereum mainnet).
function supportedNetworks() internal pure returns (string[] memory networks) {
networks = new string[](1);
networks = new string[](2);
networks[0] = LibRainDeploy.BASE;
networks[1] = ETHEREUM;
}
}
Loading
Loading