diff --git a/.github/scripts/create-upgrade-announcement.js b/.github/scripts/create-upgrade-announcement.js new file mode 100644 index 00000000..b0695bcb --- /dev/null +++ b/.github/scripts/create-upgrade-announcement.js @@ -0,0 +1,440 @@ +#!/usr/bin/env node +/** + * Create FWSS Contract Upgrade Release Issue + * + * Generates a release issue that combines user-facing upgrade information + * with a release engineer checklist (similar to Lotus release issues). + * + * See help text below for more info. + */ + +const https = require("https"); + +// Parse command line arguments +const args = process.argv.slice(2); +const dryRun = args.includes("--dry-run"); +const showHelp = args.includes("--help") || args.includes("-h"); + +if (showHelp) { + console.log(` +Create FWSS Contract Upgrade Release Issue + +Usage: + node create-upgrade-announcement.js [options] + +Options: + --dry-run Output issue text without creating an issue + --help Show this help message + +Environment variables: + NETWORK Target network (Calibnet or Mainnet) + UPGRADE_TYPE Type of upgrade (Routine or Breaking Change) + AFTER_EPOCH Block number after which upgrade can execute + CHANGELOG_PR PR number with changelog updates + CHANGES_SUMMARY Summary of changes (use | for multiple lines) + ACTION_REQUIRED Action required for integrators (default: None) + UPGRADE_REGISTRY Also upgrading ServiceProviderRegistry? (true/false, default: false, rare) + UPGRADE_STATE_VIEW Also redeploying StateView? (true/false, default: false, rare) + RELEASE_TAG Release tag (optional, usually added after upgrade completes) + GITHUB_TOKEN GitHub token (required when not using --dry-run) + GITHUB_REPOSITORY Repository in format owner/repo (required when not using --dry-run) + +Example: + NETWORK=Calibnet UPGRADE_TYPE=Routine AFTER_EPOCH=12345 \\ + CHANGELOG_PR=100 CHANGES_SUMMARY="Fix bug|Add feature" \\ + node create-upgrade-announcement.js --dry-run +`); + process.exit(0); +} + +// Get configuration from environment +const config = { + network: process.env.NETWORK, + upgradeType: process.env.UPGRADE_TYPE, + afterEpoch: process.env.AFTER_EPOCH, + changelogPr: process.env.CHANGELOG_PR, + changesSummary: process.env.CHANGES_SUMMARY, + actionRequired: process.env.ACTION_REQUIRED || "None", + upgradeRegistry: process.env.UPGRADE_REGISTRY === "true", + upgradeStateView: process.env.UPGRADE_STATE_VIEW === "true", + releaseTag: process.env.RELEASE_TAG || "", + githubToken: process.env.GITHUB_TOKEN, + githubRepository: process.env.GITHUB_REPOSITORY, + // Optional: pre-computed time estimate (from workflow) + timeEstimate: process.env.TIME_ESTIMATE, +}; + +// Validate required fields +function validateConfig() { + const required = ["network", "upgradeType", "afterEpoch", "changelogPr", "changesSummary"]; + const missing = required.filter((key) => !config[key]); + + if (missing.length > 0) { + console.error(`Error: Missing required environment variables: ${missing.join(", ")}`); + console.error("Run with --help for usage information."); + process.exit(1); + } + + if (!dryRun) { + if (!config.githubToken) { + console.error("Error: GITHUB_TOKEN is required when not using --dry-run"); + process.exit(1); + } + if (!config.githubRepository) { + console.error("Error: GITHUB_REPOSITORY is required when not using --dry-run"); + process.exit(1); + } + } +} + +// Fetch current epoch from Filecoin RPC +async function getCurrentEpoch(network) { + const rpcUrl = + network === "Mainnet" + ? "https://api.node.glif.io/rpc/v1" + : "https://api.calibration.node.glif.io/rpc/v1"; + + return new Promise((resolve, reject) => { + const url = new URL(rpcUrl); + const postData = JSON.stringify({ + jsonrpc: "2.0", + method: "eth_blockNumber", + params: [], + id: 1, + }); + + const options = { + hostname: url.hostname, + path: url.pathname, + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(postData), + }, + }; + + const req = https.request(options, (res) => { + let data = ""; + res.on("data", (chunk) => (data += chunk)); + res.on("end", () => { + try { + const result = JSON.parse(data); + if (result.result) { + resolve(parseInt(result.result, 16)); + } else { + reject(new Error("Invalid RPC response")); + } + } catch (e) { + reject(e); + } + }); + }); + + req.on("error", reject); + req.write(postData); + req.end(); + }); +} + +// Calculate estimated execution time +async function calculateTimeEstimate(network, afterEpoch) { + // If pre-computed estimate is provided, use it + if (config.timeEstimate) { + return config.timeEstimate; + } + + try { + const currentEpoch = await getCurrentEpoch(network); + const epochsRemaining = afterEpoch - currentEpoch; + + if (epochsRemaining < 0) { + return "Immediately (epoch already passed)"; + } + + // Filecoin has ~30 second block times + const secondsRemaining = epochsRemaining * 30; + const hours = Math.floor(secondsRemaining / 3600); + const minutes = Math.floor((secondsRemaining % 3600) / 60); + + const futureDate = new Date(Date.now() + secondsRemaining * 1000); + const dateStr = futureDate.toISOString().replace("T", " ").substring(0, 16) + " UTC"; + + return `~${dateStr} (~${hours}h ${minutes}m from current epoch ${currentEpoch})`; + } catch (error) { + console.error("Warning: Could not fetch current epoch:", error.message); + return "Unknown (could not fetch current epoch)"; + } +} + +// Format changes summary from pipe-separated to bullet points +function formatChanges(changesSummary) { + return changesSummary + .split("|") + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .map((line) => `- ${line}`) + .join("\n"); +} + +// Build the list of contracts being upgraded (FWSS is always included) +function buildContractsList() { + const contracts = ["FilecoinWarmStorageService"]; + + if (config.upgradeRegistry) { + contracts.push("ServiceProviderRegistry"); + } + if (config.upgradeStateView) { + contracts.push("FilecoinWarmStorageServiceStateView"); + } + + return contracts; +} + +// Generate issue title +function generateTitle() { + return `[Release] FWSS ${config.network} Upgrade - Epoch ${config.afterEpoch}`; +} + +// Generate issue body +function generateBody(timeEstimate) { + const [owner, repo] = (config.githubRepository || "OWNER/REPO").split("/"); + const baseUrl = `https://github.com/${owner}/${repo}`; + + const changelogPrLink = `${baseUrl}/pull/${config.changelogPr}`; + const changelogLink = `${baseUrl}/blob/main/CHANGELOG.md`; + const upgradeProcessLink = `${baseUrl}/blob/main/service_contracts/tools/UPGRADE-PROCESS.md`; + const releaseLink = config.releaseTag ? `${baseUrl}/releases/tag/${config.releaseTag}` : null; + + const changes = formatChanges(config.changesSummary); + const contracts = buildContractsList(); + const isMainnet = config.network === "Mainnet"; + const isBreaking = config.upgradeType === "Breaking Change"; + + // Build contracts checklist for the release checklist section + const deployChecklist = contracts + .map((c) => { + if (c === "FilecoinWarmStorageService") { + return "- [ ] Deploy FWSS implementation: `./warm-storage-deploy-implementation.sh`"; + } else if (c === "ServiceProviderRegistry") { + return "- [ ] Deploy Registry implementation: `./service-provider-registry-deploy.sh`"; + } else if (c === "FilecoinWarmStorageServiceStateView") { + return "- [ ] Deploy StateView: `./warm-storage-deploy-view.sh`"; + } + return `- [ ] Deploy ${c}`; + }) + .join("\n"); + + const announceChecklist = contracts + .map((c) => { + if (c === "FilecoinWarmStorageService") { + return "- [ ] Announce FWSS upgrade: `./warm-storage-announce-upgrade.sh`"; + } else if (c === "ServiceProviderRegistry") { + return "- [ ] Announce Registry upgrade: `./service-provider-registry-announce-upgrade.sh`"; + } + return null; + }) + .filter(Boolean) + .join("\n"); + + const executeChecklist = contracts + .map((c) => { + if (c === "FilecoinWarmStorageService") { + return "- [ ] Execute FWSS upgrade: `./warm-storage-execute-upgrade.sh`"; + } else if (c === "ServiceProviderRegistry") { + return "- [ ] Execute Registry upgrade: `./service-provider-registry-execute-upgrade.sh`"; + } + return null; + }) + .filter(Boolean) + .join("\n"); + + return `## Overview + +| Field | Value | +|-------|-------| +| **Network** | ${config.network} | +| **Upgrade Type** | ${config.upgradeType} | +| **Target Epoch** | ${config.afterEpoch} | +| **Estimated Execution** | ${timeEstimate} | +| **Changelog PR** | ${changelogPrLink} | +${releaseLink ? `| **Release** | ${releaseLink} |` : ""} + +### Contracts in Scope +${contracts.map((c) => `- ${c}`).join("\n")} + +### Changes +${changes} + +### Action Required for Integrators +${config.actionRequired} + +--- + +## Release Checklist + +> Full process details: [UPGRADE-PROCESS.md](${upgradeProcessLink}) + +### Phase 1: Prepare +- [ ] Changelog entry prepared in [CHANGELOG.md](${changelogLink}) +- [ ] Version string updated in contracts (if applicable) +- [ ] Upgrade PR created: #${config.changelogPr} +${isBreaking ? "- [ ] Migration guide prepared for breaking changes" : ""} + +### Phase 2: Calibnet Rehearsal +${isMainnet ? `
+Calibnet steps (expand) + +` : ""}**Deploy Implementation** +${deployChecklist} +- [ ] Commit updated \`deployments.json\` + +**Announce Upgrade** +${announceChecklist} + +**Execute Upgrade** +${executeChecklist} +- [ ] Verify on Blockscout +${isMainnet ? ` +
+` : ""} +${ + isMainnet + ? `### Phase 3: Mainnet Deployment +${deployChecklist} +- [ ] Commit updated \`deployments.json\` + +### Phase 4: Announce Mainnet Upgrade +${announceChecklist} +- [ ] Notify stakeholders (post in relevant channels) + +### Phase 5: Execute Mainnet Upgrade +> ⏳ Wait until after epoch ${config.afterEpoch} + +${executeChecklist} +- [ ] Verify on Blockscout +` + : "" +} +### Phase ${isMainnet ? "6" : "3"}: Verify and Release +- [ ] Verify upgrade on Blockscout +- [ ] Confirm \`deployments.json\` is up to date +- [ ] Merge changelog PR: #${config.changelogPr} +- [ ] Tag release: \`git tag vX.Y.Z && git push origin vX.Y.Z\` +- [ ] Create GitHub Release with changelog +- [ ] Merge auto-generated PRs in [filecoin-cloud](https://github.com/FilOzone/filecoin-cloud/pulls) so docs.filecoin.cloud and filecoin.cloud reflect new contract versions +- [ ] Create "Upgrade Synapse to use newest contracts" issue +- [ ] Update this issue with release link +- [ ] Close this issue + +--- + +### Resources +- [Changelog](${changelogLink}) +- [Upgrade Process Documentation](${upgradeProcessLink}) +${releaseLink ? `- [Release](${releaseLink})` : ""} + +### Deployed Addresses + +| Contract | Network | Address | +|----------|---------|---------| +| | | |`; +} + +// Generate labels for the issue +function generateLabels() { + const labels = ["release"]; + if (config.upgradeType === "Breaking Change") { + labels.push("breaking-change"); + } + return labels; +} + +// Create GitHub issue +async function createGitHubIssue(title, body, labels) { + const [owner, repo] = config.githubRepository.split("/"); + + return new Promise((resolve, reject) => { + const postData = JSON.stringify({ title, body, labels }); + + const options = { + hostname: "api.github.com", + path: `/repos/${owner}/${repo}/issues`, + method: "POST", + headers: { + Authorization: `Bearer ${config.githubToken}`, + Accept: "application/vnd.github+json", + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(postData), + "User-Agent": "create-upgrade-announcement-script", + "X-GitHub-Api-Version": "2022-11-28", + }, + }; + + const req = https.request(options, (res) => { + let data = ""; + res.on("data", (chunk) => (data += chunk)); + res.on("end", () => { + try { + const result = JSON.parse(data); + if (res.statusCode === 201) { + resolve(result); + } else { + reject(new Error(`GitHub API error: ${res.statusCode} - ${result.message || data}`)); + } + } catch (e) { + reject(e); + } + }); + }); + + req.on("error", reject); + req.write(postData); + req.end(); + }); +} + +// Main execution +async function main() { + validateConfig(); + + const timeEstimate = await calculateTimeEstimate(config.network, parseInt(config.afterEpoch)); + const title = generateTitle(); + const body = generateBody(timeEstimate); + const labels = generateLabels(); + + if (dryRun) { + console.log("=== DRY RUN - Issue Preview ===\n"); + console.log(`Title: ${title}\n`); + console.log(`Labels: ${labels.join(", ")}\n`); + console.log("--- Body ---"); + console.log(body); + console.log("\n=== End of Preview ==="); + + // Output in GitHub Actions format if running in that context + if (process.env.GITHUB_OUTPUT) { + const fs = require("fs"); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `title=${title}\n`); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `labels=${labels.join(",")}\n`); + // For multiline body, use delimiter syntax + fs.appendFileSync(process.env.GITHUB_OUTPUT, `body<> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Network**: ${{ inputs.network }}" >> $GITHUB_STEP_SUMMARY + echo "**After Epoch**: ${{ inputs.after_epoch }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ inputs.dry_run }}" = "true" ]; then + echo "This was a dry run. No issue was created." >> $GITHUB_STEP_SUMMARY + else + echo "See the created issue for full details." >> $GITHUB_STEP_SUMMARY + fi diff --git a/service_contracts/README.md b/service_contracts/README.md index 0e4e6813..40a85ad1 100644 --- a/service_contracts/README.md +++ b/service_contracts/README.md @@ -16,8 +16,8 @@ This directory contains the smart contracts for different Filecoin services usin - `FilecoinWarmStorageService.t.sol` - Tests for the service contract - `tools/` - Deployment and utility scripts - `create_data_set_with_payments.sh` - Script to create data sets with payments - - `deploy-warm-storage-calibnet.sh` - Deployment script for Warm Storage service on Calibnet - - `deploy-all-warm-storage-calibnet.sh` - Deployment script for all Warm Storage contracts on Calibnet + - `warm-storage-deploy-calibnet.sh` - Deployment script for Warm Storage service on Calibnet + - `warm-storage-deploy-all.sh` - Deployment script for all Warm Storage contracts - Note: deployment scripts now deploy and link `SignatureVerificationLib` when deploying `FilecoinWarmStorageService`. The scripts will deploy `src/lib/SignatureVerificationLib.sol` (or simulate it in dry-run) and pass the library address to `forge create` via the `--libraries` flag so the service implementation is correctly linked. diff --git a/service_contracts/src/FilecoinWarmStorageService.sol b/service_contracts/src/FilecoinWarmStorageService.sol index 947aac14..f9340ecb 100644 --- a/service_contracts/src/FilecoinWarmStorageService.sol +++ b/service_contracts/src/FilecoinWarmStorageService.sol @@ -60,7 +60,7 @@ contract FilecoinWarmStorageService is EIP712Upgradeable { // Version tracking - string public constant VERSION = "1.0.0"; + string public constant VERSION = "1.1.0"; // ========================================================================= // Events diff --git a/service_contracts/src/ServiceProviderRegistry.sol b/service_contracts/src/ServiceProviderRegistry.sol index b0cc214c..c658e71c 100644 --- a/service_contracts/src/ServiceProviderRegistry.sol +++ b/service_contracts/src/ServiceProviderRegistry.sol @@ -43,7 +43,7 @@ contract ServiceProviderRegistry is } /// @notice Version of the contract implementation - string public constant VERSION = "1.0.0"; + string public constant VERSION = "1.1.0"; /// @notice Maximum length for service URL uint256 private constant MAX_SERVICE_URL_LENGTH = 256; diff --git a/service_contracts/test/FilecoinWarmStorageService.t.sol b/service_contracts/test/FilecoinWarmStorageService.t.sol index dca54a07..5df116de 100644 --- a/service_contracts/test/FilecoinWarmStorageService.t.sol +++ b/service_contracts/test/FilecoinWarmStorageService.t.sol @@ -5387,7 +5387,7 @@ contract FilecoinWarmStorageServiceUpgradeTest is Test { if (logs[i].topics[0] == expectedTopic) { // Decode and verify the event data (string memory version, address implementation) = abi.decode(logs[i].data, (string, address)); - assertEq(version, "1.0.0", "Version should be 1.0.0"); + assertEq(version, "1.1.0", "Version should be 1.1.0"); assertTrue(implementation != address(0), "Implementation address should not be zero"); foundEvent = true; break; diff --git a/service_contracts/test/ServiceProviderRegistry.t.sol b/service_contracts/test/ServiceProviderRegistry.t.sol index ae4315a1..f69aeaa7 100644 --- a/service_contracts/test/ServiceProviderRegistry.t.sol +++ b/service_contracts/test/ServiceProviderRegistry.t.sol @@ -38,7 +38,7 @@ contract ServiceProviderRegistryTest is MockFVMTest { function testInitialState() public view { // Check version - assertEq(registry.VERSION(), "1.0.0", "Version should be 1.0.0"); + assertEq(registry.VERSION(), "1.1.0", "Version should be 1.1.0"); // Check owner assertEq(registry.owner(), owner, "Service provider should be deployer"); diff --git a/service_contracts/test/ServiceProviderRegistryFull.t.sol b/service_contracts/test/ServiceProviderRegistryFull.t.sol index 5211ff56..9fe0c9d8 100644 --- a/service_contracts/test/ServiceProviderRegistryFull.t.sol +++ b/service_contracts/test/ServiceProviderRegistryFull.t.sol @@ -85,7 +85,7 @@ contract ServiceProviderRegistryFullTest is MockFVMTest { // ========== Initial State Tests ========== function testInitialState() public view { - assertEq(registry.VERSION(), "1.0.0", "Version should be 1.0.0"); + assertEq(registry.VERSION(), "1.1.0", "Version should be 1.1.0"); assertEq(registry.owner(), owner, "Service provider should be deployer"); assertEq(registry.getNextProviderId(), 1, "Next provider ID should start at 1"); assertEq(registry.REGISTRATION_FEE(), 5 ether, "Registration fee should be 5 FIL"); diff --git a/service_contracts/tools/README.md b/service_contracts/tools/README.md index 9ad4a0dd..0aeafd9b 100644 --- a/service_contracts/tools/README.md +++ b/service_contracts/tools/README.md @@ -2,25 +2,51 @@ This directory contains scripts for deploying and upgrading the FilecoinWarmStorageService contract on Calibration testnet and Mainnet. +> **For detailed upgrade procedures**, see [UPGRADE-PROCESS.md](./UPGRADE-PROCESS.md). + ## Scripts Overview -### Available Scripts +Scripts are organized with prefixes for better discoverability: + +### Warm Storage Scripts + +| Script | Description | +|--------|-------------| +| `warm-storage-deploy-all.sh` | Deploy all contracts (PDPVerifier, FilecoinPayV1, FWSS, etc.) | +| `warm-storage-deploy-implementation.sh` | Deploy FWSS implementation only (for upgrades) | +| `warm-storage-deploy-view.sh` | Deploy FilecoinWarmStorageServiceStateView | +| `warm-storage-deploy-calibnet.sh` | Deploy FWSS only (requires existing dependencies) | +| `warm-storage-announce-upgrade.sh` | Announce a planned FWSS upgrade | +| `warm-storage-execute-upgrade.sh` | Execute a previously announced FWSS upgrade | +| `warm-storage-set-view.sh` | Set the StateView address on FWSS | + +### Service Provider Registry Scripts + +| Script | Description | +|--------|-------------| +| `service-provider-registry-deploy.sh` | Deploy ServiceProviderRegistry | +| `service-provider-registry-announce-upgrade.sh` | Announce a planned registry upgrade | +| `service-provider-registry-execute-upgrade.sh` | Execute a previously announced registry upgrade | + +### Other Scripts -- `deploy-warm-storage-calibnet.sh` - Deploy FilecoinWarmStorageService only (requires existing PDPVerifier and FilecoinPayV1 contracts) -- `deploy-all-warm-storage.sh` - Deploy all contracts to either Calibnet or Mainnet -- `upgrade-warm-storage-calibnet.sh` - Upgrade existing FilecoinWarmStorageService contract with new proving period parameters +| Script | Description | +|--------|-------------| +| `session-key-registry-deploy.sh` | Deploy SessionKeyRegistry | +| `provider-id-set-deploy.sh` | Deploy ProviderIdSet | ### Usage ```bash -# Deploy to Calibnet -./tools/deploy-warm-storage-calibnet.sh - # Deploy all contracts -./tools/deploy-all-warm-storage.sh +./tools/warm-storage-deploy-all.sh + +# Deploy to Calibnet (FWSS only) +./tools/warm-storage-deploy-calibnet.sh -# Upgrade existing deployment -./tools/upgrade-warm-storage-calibnet.sh +# Upgrade existing deployment (see UPGRADE-PROCESS.md for details) +./tools/warm-storage-announce-upgrade.sh # Step 1: Announce +./tools/warm-storage-execute-upgrade.sh # Step 2: Execute (after AFTER_EPOCH) ``` ## Deployment Parameters @@ -115,17 +141,14 @@ These scripts now follow forge/cast's environment variable conventions. Set the - `ETH_FROM` - Optional: address to use as deployer (forge/cast default is taken from the keystore) ### Required for specific scripts: -- `deploy-warm-storage-calibnet.sh` requires: - +- `warm-storage-deploy-calibnet.sh` requires: - `PDP_VERIFIER_PROXY_ADDRESS` - Address of deployed PDPVerifier contract - `FILECOIN_PAY_ADDRESS` - Address of deployed FilecoinPayV1 contract - -- `deploy-all-warm-storage.sh` requires: +- `warm-storage-deploy-all.sh` requires: - `CHALLENGE_FINALITY` - Challenge finality parameter for PDPVerifier -- `upgrade-warm-storage-calibnet.sh` requires: - - `WARM_STORAGE_SERVICE_PROXY_ADDRESS` - Address of existing FilecoinWarmStorageService proxy to upgrade +- Upgrade scripts - see [UPGRADE-PROCESS.md](./UPGRADE-PROCESS.md) for complete environment variable reference ## Usage Examples @@ -143,7 +166,7 @@ export CHALLENGE_FINALITY="10" # Use "150" for mainnet export MAX_PROVING_PERIOD="240" # 240 epochs for calibnet, 2880 for mainnet export CHALLENGE_WINDOW_SIZE="20" # 20 epochs for calibnet, 60 for mainnet -./deploy-all-warm-storage.sh +./warm-storage-deploy-all.sh ``` ### Deploy FilecoinWarmStorageService Only @@ -155,30 +178,29 @@ export ETH_RPC_URL="https://api.calibration.node.glif.io/rpc/v1" export PDP_VERIFIER_PROXY_ADDRESS="0x123..." export FILECOIN_PAY_ADDRESS="0x456..." -./deploy-warm-storage-calibnet.sh +./warm-storage-deploy-calibnet.sh ``` ### Upgrade Existing Contract -```bash -export ETH_KEYSTORE="/path/to/keystore.json" -export PASSWORD="your-password" -export ETH_RPC_URL="https://api.calibration.node.glif.io/rpc/v1" -export WARM_STORAGE_SERVICE_PROXY_ADDRESS="0x789..." +See [UPGRADE-PROCESS.md](./UPGRADE-PROCESS.md) for the complete two-step upgrade workflow. -# Optional: Custom proving periods -export MAX_PROVING_PERIOD="240" # 240 epochs for calibnet, 2880 for mainnet -export CHALLENGE_WINDOW_SIZE="20" # 20 epochs for calibnet, 60 for mainnet +## Contract Upgrade Process -./upgrade-warm-storage-calibnet.sh -``` +The FilecoinWarmStorageService and ServiceProviderRegistry contracts use a **two-step upgrade process** for security: -## Contract Upgrade Process +1. **Announce**: Call `announcePlannedUpgrade()` with the new implementation address and a future epoch +2. **Execute**: After the announced epoch, call `upgradeToAndCall()` to complete the upgrade + +This gives stakeholders time to review changes before execution. -The FilecoinWarmStorageService contract uses OpenZeppelin's upgradeable pattern. When upgrading: +**For complete upgrade documentation**, including: +- Step-by-step upgrade workflows +- Environment variable reference +- Immutable dependency handling +- Verification procedures -1. **Deploy new implementation**: The script deploys a new implementation contract -2. **Upgrade proxy**: Uses `upgradeToAndCall` to point the proxy to the new implementation +See [UPGRADE-PROCESS.md](./UPGRADE-PROCESS.md). ## Testing diff --git a/service_contracts/tools/UPGRADE-PROCESS.md b/service_contracts/tools/UPGRADE-PROCESS.md new file mode 100644 index 00000000..7584fce6 --- /dev/null +++ b/service_contracts/tools/UPGRADE-PROCESS.md @@ -0,0 +1,272 @@ +# FWSS Contract Upgrade Process + +This document describes the upgrade process for FilecoinWarmStorageService (FWSS), organized as a phase-based runbook. Most upgrades only involve the FWSS implementation contract. + +## Contract Overview + +| Contract | Upgradeable? | Notes | +|----------|--------------|-------| +| **FilecoinWarmStorageService (FWSS)** | Yes (UUPS, two-step) | Primary contract, most upgrades are here | +| ServiceProviderRegistry | Yes (UUPS, two-step) | Rarely upgraded separately | +| FilecoinWarmStorageServiceStateView | No (redeploy) | Helper contract, redeploy if view logic changes | +| PDPVerifier | Yes (ERC1967) | Via [pdp repo](https://github.com/FilOzone/pdp) | +| FilecoinPayV1, SessionKeyRegistry | No (immutable) | Not expected to change | + +**UUPS, two-step** — These contracts use the [UUPS (ERC-1822)](https://eips.ethereum.org/EIPS/eip-1822) proxy pattern via OpenZeppelin's `UUPSUpgradeable`, where the upgrade authorization logic lives in the *implementation* contract rather than the proxy. On top of standard UUPS, we add a **two-step upgrade mechanism**: the owner must first call `announcePlannedUpgrade()` to record the new implementation address and a future epoch, then wait for that epoch to pass before `upgradeToAndCall()` will succeed. + +> For upgrading ServiceProviderRegistry or redeploying StateView, see [Upgrading Other Contracts](#upgrading-other-contracts). + +## Two-Step Upgrade Mechanism + +FWSS uses a two-step upgrade to give stakeholders notice: + +1. **Announce** - Call `announcePlannedUpgrade()` with the new implementation address and `AFTER_EPOCH` +2. **Execute** - After `AFTER_EPOCH` passes, call `upgradeToAndCall()` to complete the upgrade + +## Choosing AFTER_EPOCH + +| Upgrade Type | Minimum Notice | Recommended | +|--------------|----------------|-------------| +| Routine (bug fixes, minor features) | ~24 hours (~2880 epochs) | 1-2 days | +| Breaking changes | ~1 week (~20160 epochs) | 1-2 weeks | + +Calculate an `AFTER_EPOCH`: +```bash +UPGRADE_WAIT_DURATION_EPOCHS=2880 # ~24 hours +CURRENT_EPOCH=$(cast block-number --rpc-url "$ETH_RPC_URL") +AFTER_EPOCH=$((CURRENT_EPOCH + UPGRADE_WAIT_DURATION_EPOCHS)) +echo "Current: $CURRENT_EPOCH, Upgrade after: $AFTER_EPOCH" +``` + +**Considerations:** +- Allow time for stakeholder review +- Avoid weekends/holidays for mainnet upgrades +- Calibnet can use shorter notice periods for testing + +--- + +## Phase 1: Prepare + +1. **Prepare changelog entry** in [`CHANGELOG.md`](../../CHANGELOG.md): + - Document all changes since [last release](https://github.com/FilOzone/filecoin-services/releases) + - Mark breaking changes clearly + - Include migration notes if needed + +2. **Update the [version](https://github.com/FilOzone/filecoin-services/blob/main/service_contracts/src/FilecoinWarmStorageService.sol#L63)** string in the contract. + +3. **Create an upgrade PR** with your changelog updates. + - Example title: `feat: FWSS v1.2.0 upgrade` + +4. **Create release issue** using the [Create Release Issue](https://github.com/FilOzone/filecoin-services/actions/workflows/create-upgrade-announcement-issue.yml) GitHub Action. + - Or preview locally: `node .github/scripts/create-upgrade-announcement.js --dry-run` + +## Phase 2: Calibnet Rehearsal + +Always test the upgrade on Calibnet before mainnet. + +### Deploy Implementation + +```bash +cd service_contracts/tools +export ETH_RPC_URL="https://api.calibration.node.glif.io/rpc/v1" + +./warm-storage-deploy-implementation.sh +``` + +The script updates `deployments.json` automatically. Commit the changes in the branch of the "upgrade PR" above. + +### Announce Upgrade + +```bash +export WARM_STORAGE_PROXY_ADDRESS="0x..." +export NEW_WARM_STORAGE_IMPLEMENTATION_ADDRESS="0x..." +export AFTER_EPOCH="123456" + +./warm-storage-announce-upgrade.sh +``` + +### Execute Upgrade + +After `AFTER_EPOCH` passes: + +```bash +export WARM_STORAGE_PROXY_ADDRESS="0x..." +export NEW_WARM_STORAGE_IMPLEMENTATION_ADDRESS="0x..." + +./warm-storage-execute-upgrade.sh +``` + +Verify the upgrade on [Calibnet Blockscout](https://calibration.filfox.info/). + +## Phase 3: Mainnet Deployment + +```bash +cd service_contracts/tools +export ETH_RPC_URL="https://api.node.glif.io/rpc/v1" + +./warm-storage-deploy-implementation.sh +``` + +Commit the updated `deployments.json` in the branch of the "upgrade PR" above. + +## Phase 4: Announce Mainnet Upgrade + +```bash +export ETH_RPC_URL="https://api.node.glif.io/rpc/v1" +export WARM_STORAGE_PROXY_ADDRESS="0x..." +export NEW_WARM_STORAGE_IMPLEMENTATION_ADDRESS="0x..." +export AFTER_EPOCH="123456" + +./warm-storage-announce-upgrade.sh +``` + +Notify stakeholders (see [Stakeholder Communication](#stakeholder-communication)). + +## Phase 5: Execute Mainnet Upgrade + +After `AFTER_EPOCH` passes: + +```bash +export ETH_RPC_URL="https://api.node.glif.io/rpc/v1" +export WARM_STORAGE_PROXY_ADDRESS="0x..." +export NEW_WARM_STORAGE_IMPLEMENTATION_ADDRESS="0x..." + +./warm-storage-execute-upgrade.sh +``` + +## Phase 6: Verify and Release + +1. **Verify** the upgrade on [Mainnet Blockscout](https://filfox.info/). +2. **Confirm** `deployments.json` is up to date. +3. **Merge** the changelog PR. +4. **Tag release**: + ```bash + git tag v1.X.0 + git push origin v1.X.0 + ``` +5. **Create GitHub Release** with the changelog. +6. **Close the release issue** after updating it with the release link. + +--- + +## Stakeholder Communication + +> **Tip**: Use the [Create Release Issue](https://github.com/FilOzone/filecoin-services/actions/workflows/create-upgrade-announcement-issue.yml) GitHub Action to generate a release issue. Go to **Actions → Create Release Issue → Run workflow**. + +The release issue serves as both the public announcement and the release engineer's checklist. It includes: +- Overview with network, epoch, estimated time, and changelog link +- Summary of changes and action required for integrators +- Full release checklist with all phases +- Deployed addresses table (filled in during the process) + +### Breaking Changes + +For breaking API changes: +- Provide extended notice period (1-2 weeks recommended) +- Create a migration guide for affected integrators +- Consider phased rollout: Calibnet first, then Mainnet after validation + +--- + +## Upgrading Other Contracts + +Most upgrades only involve FWSS. This section covers the rare cases where you need to upgrade other contracts. + +### ServiceProviderRegistry + +The registry uses the same two-step upgrade mechanism as FWSS. Only upgrade it when: +- There are changes to provider registration logic +- Storage or validation rules need updating + +**Deploy new implementation:** +```bash +./service-provider-registry-deploy.sh +``` + +**Announce upgrade:** +```bash +export REGISTRY_PROXY_ADDRESS="0x..." +export NEW_REGISTRY_IMPLEMENTATION_ADDRESS="0x..." +export AFTER_EPOCH="123456" + +./service-provider-registry-announce-upgrade.sh +``` + +**Execute upgrade (after AFTER_EPOCH):** +```bash +export SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS="0x..." +export NEW_REGISTRY_IMPLEMENTATION_ADDRESS="0x..." + +./service-provider-registry-execute-upgrade.sh +``` + +**Environment variables:** + +| Variable | Description | +|----------|-------------| +| `REGISTRY_PROXY_ADDRESS` | Address of registry proxy (for announce script) | +| `SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS` | Address of registry proxy (for upgrade script) | +| `NEW_REGISTRY_IMPLEMENTATION_ADDRESS` | Address of new implementation | +| `AFTER_EPOCH` | Block number after which upgrade can execute | + +### FilecoinWarmStorageServiceStateView + +StateView is a helper contract (not upgradeable). Redeploy it when: +- The view logic changes +- FWSS changes require updated read functions + +**Deploy new StateView:** +```bash +./warm-storage-deploy-view.sh +``` + +**Update FWSS to use new StateView (during upgrade):** +```bash +export NEW_WARM_STORAGE_VIEW_ADDRESS="0x..." +./warm-storage-execute-upgrade.sh +``` + +### Immutable Dependencies + +FWSS has these immutable dependencies that are **not expected to change**: + +```solidity +IPDPVerifier public immutable pdpVerifier; +FilecoinPayV1 public immutable paymentsContract; +IERC20Metadata public immutable usdfcTokenAddress; +address public immutable filBeamBeneficiaryAddress; +ServiceProviderRegistry public immutable serviceProviderRegistry; +SessionKeyRegistry public immutable sessionKeyRegistry; +``` + +If any of these need to change, it requires redeploying FWSS entirely. This should be announced well in advance (~2-4 weeks) with a migration plan. + +--- + +## Environment Variables Reference + +### Common Variables + +| Variable | Description | +|----------|-------------| +| `ETH_RPC_URL` | RPC endpoint | +| `ETH_KEYSTORE` | Path to Ethereum keystore file | +| `PASSWORD` | Keystore password | +| `CHAIN` | Chain ID (auto-detected if not set) | + +### FWSS Variables + +| Variable | Description | +|----------|-------------| +| `WARM_STORAGE_PROXY_ADDRESS` | Address of FWSS proxy contract | +| `NEW_WARM_STORAGE_IMPLEMENTATION_ADDRESS` | Address of new implementation | +| `AFTER_EPOCH` | Block number after which upgrade can execute | +| `NEW_WARM_STORAGE_VIEW_ADDRESS` | (Optional) New StateView address to set during upgrade | + +## Deployment Address Management + +All deployment scripts automatically load and update addresses in `deployments.json`. See [README.md](./README.md) for details on: + +- How addresses are loaded by chain ID +- Environment variable overrides +- Control flags (`SKIP_LOAD_DEPLOYMENTS`, `SKIP_UPDATE_DEPLOYMENTS`) diff --git a/service_contracts/tools/deploy-registry-calibnet.sh b/service_contracts/tools/deploy-registry-calibnet.sh deleted file mode 100755 index a48868cd..00000000 --- a/service_contracts/tools/deploy-registry-calibnet.sh +++ /dev/null @@ -1,167 +0,0 @@ -#!/bin/bash -# deploy-registry-calibnet deploys the Service Provider Registry contract to calibration net -# Assumption: ETH_KEYSTORE, PASSWORD, ETH_RPC_URL env vars are set to an appropriate eth keystore path and password -# and to a valid ETH_RPC_URL for the calibnet. -# Assumption: forge, cast, jq are in the PATH -# Assumption: called from contracts directory so forge paths work out -# - -# Get script directory and source deployments.sh -SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" -source "$SCRIPT_DIR/deployments.sh" - -echo "Deploying Service Provider Registry Contract" - -export CHAIN=314159 - -# Load deployment addresses from deployments.json -load_deployment_addresses "$CHAIN" - -if [ -z "$ETH_RPC_URL" ]; then - echo "Error: ETH_RPC_URL is not set" - exit 1 -fi - -if [ -z "$ETH_KEYSTORE" ]; then - echo "Error: ETH_KEYSTORE is not set" - exit 1 -fi - -# Optional: Check if PASSWORD is set (some users might use empty password) -if [ -z "$PASSWORD" ]; then - echo "Warning: PASSWORD is not set, using empty password" -fi - -ADDR=$(cast wallet address --password "$PASSWORD") -echo "Deploying contracts from address $ADDR" - -# Get current balance and nonce (cast will use ETH_RPC_URL) -BALANCE=$(cast balance "$ADDR") -echo "Deployer balance: $BALANCE" - -NONCE="$(cast nonce "$ADDR")" -echo "Starting nonce: $NONCE" - -# Deploy ServiceProviderRegistry implementation -SPR_INIT_COUNTER=1 -echo "" -echo "=== STEP 1: Deploying ServiceProviderRegistry Implementation ===" -SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS=$(forge create --password "$PASSWORD" --broadcast --nonce $NONCE src/ServiceProviderRegistry.sol:ServiceProviderRegistry --optimizer-runs 1 --via-ir --constructor-args $SPR_INIT_COUNTER | grep "Deployed to" | awk '{print $3}') -if [ -z "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" ]; then - echo "Error: Failed to extract ServiceProviderRegistry implementation address" - exit 1 -fi -echo "✓ ServiceProviderRegistry implementation deployed at: $SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" -NONCE=$(expr $NONCE + "1") - -# Deploy ServiceProviderRegistry proxy -echo "" -echo "=== STEP 2: Deploying ServiceProviderRegistry Proxy ===" -# Initialize with no parameters for basic initialization -INIT_DATA=$(cast calldata "initialize()") -echo "Initialization calldata: $INIT_DATA" - -REGISTRY_PROXY_ADDRESS=$(forge create --password "$PASSWORD" --broadcast --nonce $NONCE lib/pdp/src/ERC1967Proxy.sol:MyERC1967Proxy --constructor-args $SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS $INIT_DATA --optimizer-runs 1 --via-ir | grep "Deployed to" | awk '{print $3}') -if [ -z "$REGISTRY_PROXY_ADDRESS" ]; then - echo "Error: Failed to extract ServiceProviderRegistry proxy address" - exit 1 -fi -echo "✓ ServiceProviderRegistry proxy deployed at: $REGISTRY_PROXY_ADDRESS" - -# Verify deployment by calling version() on the proxy -echo "" -echo "=== STEP 3: Verifying Deployment ===" -VERSION=$(cast call $REGISTRY_PROXY_ADDRESS "version()(string)") -if [ -z "$VERSION" ]; then - echo "Warning: Could not verify contract version" -else - echo "✓ Contract version: $VERSION" -fi - -# Get registration fee -FEE=$(cast call $REGISTRY_PROXY_ADDRESS "REGISTRATION_FEE()(uint256)") -if [ -z "$FEE" ]; then - echo "Warning: Could not retrieve registration fee" - FEE_IN_FIL="unknown" -else - echo "✓ Registration fee: $FEE attoFIL" - FEE_IN_FIL="$FEE attoFIL" -fi - -# Get burn actor address -BURN_ACTOR=$(cast call $REGISTRY_PROXY_ADDRESS "BURN_ACTOR()(address)") -if [ -z "$BURN_ACTOR" ]; then - echo "Warning: Could not retrieve burn actor address" -else - echo "✓ Burn actor address: $BURN_ACTOR" -fi - -# Get contract version (this should be used instead of hardcoded version) -CONTRACT_VERSION=$(cast call $REGISTRY_PROXY_ADDRESS "VERSION()(string)") -if [ -z "$CONTRACT_VERSION" ]; then - echo "Warning: Could not retrieve contract version" - CONTRACT_VERSION="Unknown" -fi - -# Get contract version (this should be used instead of hardcoded version) -CONTRACT_VERSION=$(cast call $REGISTRY_PROXY_ADDRESS "VERSION()(string)") -if [ -z "$CONTRACT_VERSION" ]; then - echo "Warning: Could not retrieve contract version" - CONTRACT_VERSION="Unknown" -fi - -# Summary of deployed contracts -echo "" -echo "==========================================" -echo "=== DEPLOYMENT SUMMARY ===" -echo "==========================================" -echo "ServiceProviderRegistry Implementation: $SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" -echo "ServiceProviderRegistry Proxy: $REGISTRY_PROXY_ADDRESS" -echo "==========================================" - -# Update deployments.json -if [ -n "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" ]; then - update_deployment_address "$CHAIN" "SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" -fi -if [ -n "$REGISTRY_PROXY_ADDRESS" ]; then - update_deployment_address "$CHAIN" "SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS" "$REGISTRY_PROXY_ADDRESS" -fi -if [ -n "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" ] || [ -n "$REGISTRY_PROXY_ADDRESS" ]; then - update_deployment_metadata "$CHAIN" -fi -echo "" -echo "Contract Details:" -echo " - Version: $CONTRACT_VERSION" -echo " - Registration Fee: $FEE_IN_FIL (burned)" -echo " - Burn Actor: $BURN_ACTOR" -echo " - Chain: Calibration testnet (314159)" -echo "" -echo "Next steps:" -echo "1. Save the proxy address: export REGISTRY_ADDRESS=$REGISTRY_PROXY_ADDRESS" -echo "2. Verify the deployment by calling getProviderCount() - should return 0" -echo "3. Test registration with: cast send --value attoFIL ..." -echo "4. Transfer ownership if needed using transferOwnership()" -echo "5. The registry is ready for provider registrations" -echo "" -echo "To interact with the registry:" -echo " View functions:" -echo " cast call $REGISTRY_PROXY_ADDRESS \"getProviderCount()(uint256)\"" -echo " cast call $REGISTRY_PROXY_ADDRESS \"getAllActiveProviders()(uint256[])\"" -echo " State changes (requires registration fee):" -echo " Register as provider (requires proper encoding of PDPData)" -echo "" - -# Automatic contract verification -if [ "${AUTO_VERIFY:-true}" = "true" ]; then - echo - echo "🔍 Starting automatic contract verification..." - - pushd "$(dirname $0)/.." >/dev/null - source tools/verify-contracts.sh - verify_contracts_batch "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS,src/ServiceProviderRegistry.sol:ServiceProviderRegistry" - popd >/dev/null -else - echo - echo "⏭️ Skipping automatic verification (export AUTO_VERIFY=true to enable)" -fi -echo "==========================================" diff --git a/service_contracts/tools/deploy-provider-id-set.sh b/service_contracts/tools/provider-id-set-deploy.sh similarity index 97% rename from service_contracts/tools/deploy-provider-id-set.sh rename to service_contracts/tools/provider-id-set-deploy.sh index 68631732..f60f7dfb 100755 --- a/service_contracts/tools/deploy-provider-id-set.sh +++ b/service_contracts/tools/provider-id-set-deploy.sh @@ -1,5 +1,5 @@ #!/bin/bash -# deploy-provider-id-set deploys a ProviderIdSet contract +# provider-id-set-deploy.sh deploys a ProviderIdSet contract # Assumption: ETH_KEYSTORE, PASSWORD, ETH_RPC_URL env vars are set to an appropriate eth keystore path and password # Assumption: forge, cast, jq are in the PATH # Assumption: called from contracts directory so forge paths work out diff --git a/service_contracts/tools/announce-planned-upgrade-registry.sh b/service_contracts/tools/service-provider-registry-announce-upgrade.sh similarity index 95% rename from service_contracts/tools/announce-planned-upgrade-registry.sh rename to service_contracts/tools/service-provider-registry-announce-upgrade.sh index 00542ab1..7428bdcd 100755 --- a/service_contracts/tools/announce-planned-upgrade-registry.sh +++ b/service_contracts/tools/service-provider-registry-announce-upgrade.sh @@ -1,6 +1,6 @@ #!/bin/bash -# announce-planned-upgrade-registry.sh: Announces a planned upgrade for ServiceProviderRegistry +# service-provider-registry-announce-upgrade.sh: Announces a planned upgrade for ServiceProviderRegistry # Required args: ETH_RPC_URL, SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS, ETH_KEYSTORE, PASSWORD, NEW_SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS, AFTER_EPOCH if [ -z "$ETH_RPC_URL" ]; then diff --git a/service_contracts/tools/service-provider-registry-deploy.sh b/service_contracts/tools/service-provider-registry-deploy.sh new file mode 100755 index 00000000..b6dd2c6b --- /dev/null +++ b/service_contracts/tools/service-provider-registry-deploy.sh @@ -0,0 +1,219 @@ +#!/bin/bash +# service-provider-registry-deploy.sh deploys the Service Provider Registry contract to a target network +# Assumption: ETH_KEYSTORE, PASSWORD, ETH_RPC_URL env vars are set to an appropriate eth keystore path and password +# and to a valid ETH_RPC_URL for the target network. +# Assumption: forge, cast, jq are in the PATH +# Assumption: called from contracts directory so forge paths work out +# + +# Get script directory and source deployments.sh +SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" +source "$SCRIPT_DIR/deployments.sh" + +echo "Deploying Service Provider Registry Contract" + +WITH_PROXY=false +for arg in "$@"; do + case "$arg" in + --with-proxy) + WITH_PROXY=true + ;; + -h|--help) + echo "Usage: $(basename "$0") [--with-proxy]" + echo "" + echo "Default: deploy implementation only (upgrade)." + echo " --with-proxy Also deploy a new proxy and initialize it." + exit 0 + ;; + *) + echo "Error: Unknown option '$arg'" + echo "Run with --help for usage." + exit 1 + ;; + esac +done + +if [ "$WITH_PROXY" = "true" ]; then + echo "Mode: implementation + proxy deployment" +else + echo "Mode: implementation-only deployment with no new proxy and no proxy changes" +fi + +if [ -z "$ETH_RPC_URL" ]; then + echo "Error: ETH_RPC_URL is not set" + exit 1 +fi + +# Auto-detect chain ID from RPC if not already set +if [ -z "$CHAIN" ]; then + export CHAIN=$(cast chain-id) + if [ -z "$CHAIN" ]; then + echo "Error: Failed to detect chain ID from RPC" + exit 1 + fi +fi + +# Load deployment addresses from deployments.json +load_deployment_addresses "$CHAIN" + +if [ -z "$ETH_KEYSTORE" ]; then + echo "Error: ETH_KEYSTORE is not set" + exit 1 +fi + +# Optional: Check if PASSWORD is set (some users might use empty password) +if [ -z "$PASSWORD" ]; then + echo "Warning: PASSWORD is not set, using empty password" +fi + +ADDR=$(cast wallet address --password "$PASSWORD") +echo "Deploying contracts from address $ADDR" + +# Get current balance and nonce (cast will use ETH_RPC_URL) +BALANCE=$(cast balance "$ADDR") +echo "Deployer balance: $BALANCE" + +NONCE="$(cast nonce "$ADDR")" +echo "Starting nonce: $NONCE" + +# Deploy ServiceProviderRegistry implementation +echo "" +echo "=== STEP 1: Deploying ServiceProviderRegistry Implementation ===" +SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS=$(forge create --password "$PASSWORD" --broadcast --nonce $NONCE src/ServiceProviderRegistry.sol:ServiceProviderRegistry --optimizer-runs 1 --via-ir | grep "Deployed to" | awk '{print $3}') +if [ -z "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" ]; then + echo "Error: Failed to extract ServiceProviderRegistry implementation address" + exit 1 +fi +echo "✓ ServiceProviderRegistry implementation deployed at: $SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" +NONCE=$(expr $NONCE + "1") + +if [ "$WITH_PROXY" = "true" ]; then + # Deploy ServiceProviderRegistry proxy + echo "" + echo "=== STEP 2: Deploying ServiceProviderRegistry Proxy ===" + # Initialize with no parameters for basic initialization + INIT_DATA=$(cast calldata "initialize()") + echo "Initialization calldata: $INIT_DATA" + + REGISTRY_PROXY_ADDRESS=$(forge create --password "$PASSWORD" --broadcast --nonce $NONCE lib/pdp/src/ERC1967Proxy.sol:MyERC1967Proxy --constructor-args $SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS $INIT_DATA --optimizer-runs 1 --via-ir | grep "Deployed to" | awk '{print $3}') + if [ -z "$REGISTRY_PROXY_ADDRESS" ]; then + echo "Error: Failed to extract ServiceProviderRegistry proxy address" + exit 1 + fi + echo "✓ ServiceProviderRegistry proxy deployed at: $REGISTRY_PROXY_ADDRESS" + + # Verify deployment by calling version() on the proxy + echo "" + echo "=== STEP 3: Verifying Deployment ===" + VERSION=$(cast call $REGISTRY_PROXY_ADDRESS "version()(string)") + if [ -z "$VERSION" ]; then + echo "Warning: Could not verify contract version" + else + echo "✓ Contract version: $VERSION" + fi + + # Get registration fee + FEE=$(cast call $REGISTRY_PROXY_ADDRESS "REGISTRATION_FEE()(uint256)") + if [ -z "$FEE" ]; then + echo "Warning: Could not retrieve registration fee" + FEE_IN_FIL="unknown" + else + echo "✓ Registration fee: $FEE attoFIL" + FEE_IN_FIL="$FEE attoFIL" + fi + + # Get burn actor address + BURN_ACTOR=$(cast call $REGISTRY_PROXY_ADDRESS "BURN_ACTOR()(address)") + if [ -z "$BURN_ACTOR" ]; then + echo "Warning: Could not retrieve burn actor address" + else + echo "✓ Burn actor address: $BURN_ACTOR" + fi + + # Get contract version (this should be used instead of hardcoded version) + CONTRACT_VERSION=$(cast call $REGISTRY_PROXY_ADDRESS "VERSION()(string)") + if [ -z "$CONTRACT_VERSION" ]; then + echo "Warning: Could not retrieve contract version" + CONTRACT_VERSION="Unknown" + fi +else + echo "" + echo "=== STEP 2: Skipping Proxy Deployment ===" + echo "Use --with-proxy to deploy and initialize a new proxy." + CONTRACT_VERSION="n/a" + FEE_IN_FIL="n/a" + BURN_ACTOR="n/a" +fi + +# Summary of deployed contracts +echo "" +echo "==========================================" +echo "=== DEPLOYMENT SUMMARY ===" +echo "==========================================" +echo "ServiceProviderRegistry Implementation: $SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" +if [ "$WITH_PROXY" = "true" ]; then + echo "ServiceProviderRegistry Proxy: $REGISTRY_PROXY_ADDRESS" +fi +echo "==========================================" + +# Update deployments.json +if [ -n "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" ]; then + update_deployment_address "$CHAIN" "SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" +fi +if [ "$WITH_PROXY" = "true" ] && [ -n "$REGISTRY_PROXY_ADDRESS" ]; then + update_deployment_address "$CHAIN" "SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS" "$REGISTRY_PROXY_ADDRESS" +fi +if [ -n "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS" ] || [ -n "$REGISTRY_PROXY_ADDRESS" ]; then + update_deployment_metadata "$CHAIN" +fi +echo "" +echo "Contract Details:" +echo " - Version: $CONTRACT_VERSION" +echo " - Registration Fee: $FEE_IN_FIL (burned)" +echo " - Burn Actor: $BURN_ACTOR" +CHAIN_LABEL="unknown" +if [ "$CHAIN" = "314159" ]; then + CHAIN_LABEL="Calibration testnet (314159)" +elif [ "$CHAIN" = "314" ]; then + CHAIN_LABEL="Filecoin mainnet (314)" +else + CHAIN_LABEL="Chain ID $CHAIN" +fi +echo " - Chain: $CHAIN_LABEL" +echo "" +echo "Next steps:" +if [ "$WITH_PROXY" = "true" ]; then + echo "1. Save the proxy address: export REGISTRY_ADDRESS=$REGISTRY_PROXY_ADDRESS" + echo "2. Verify the deployment by calling getProviderCount() - should return 0" + echo "3. Test registration with: cast send --value attoFIL ..." + echo "4. Transfer ownership if needed using transferOwnership()" + echo "5. The registry is ready for provider registrations" +else + echo "1. Save the implementation address for upgrade announcement" + echo "2. Proceed with service-provider-registry-announce-upgrade.sh" +fi +echo "" +if [ "$WITH_PROXY" = "true" ]; then + echo "To interact with the registry:" + echo " View functions:" + echo " cast call $REGISTRY_PROXY_ADDRESS \"getProviderCount()(uint256)\"" + echo " cast call $REGISTRY_PROXY_ADDRESS \"getAllActiveProviders()(uint256[])\"" + echo " State changes (requires registration fee):" + echo " Register as provider (requires proper encoding of PDPData)" + echo "" +fi + +# Automatic contract verification +if [ "${AUTO_VERIFY:-true}" = "true" ]; then + echo + echo "🔍 Starting automatic contract verification..." + + pushd "$(dirname $0)/.." >/dev/null + source tools/verify-contracts.sh + verify_contracts_batch "$SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS,src/ServiceProviderRegistry.sol:ServiceProviderRegistry" + popd >/dev/null +else + echo + echo "⏭️ Skipping automatic verification (export AUTO_VERIFY=true to enable)" +fi +echo "==========================================" diff --git a/service_contracts/tools/upgrade-registry.sh b/service_contracts/tools/service-provider-registry-execute-upgrade.sh similarity index 96% rename from service_contracts/tools/upgrade-registry.sh rename to service_contracts/tools/service-provider-registry-execute-upgrade.sh index fbd7f9ca..96d3e63c 100755 --- a/service_contracts/tools/upgrade-registry.sh +++ b/service_contracts/tools/service-provider-registry-execute-upgrade.sh @@ -1,6 +1,6 @@ #!/bin/bash -# upgrade-registry.sh: Completes a pending upgrade for ServiceProviderRegistry +# service-provider-registry-execute-upgrade.sh: Completes a pending upgrade for ServiceProviderRegistry # Required args: ETH_RPC_URL, SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS, ETH_KEYSTORE, PASSWORD, NEW_SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS # Optional args: NEW_VERSION # Calculated if unset: CHAIN @@ -91,13 +91,13 @@ if [ $CAST_CALL_EXIT_CODE -eq 0 ] && [ -n "$UPGRADE_PLAN_OUTPUT" ]; then # Method exists but returns zero - no planned upgrade or already completed # On new contracts, _authorizeUpgrade requires a planned upgrade, so one-step will fail echo "No planned upgrade detected (nextUpgrade returns zero)" - echo "Error: This contract requires a planned upgrade. Please call announce-planned-upgrade-registry.sh first." + echo "Error: This contract requires a planned upgrade. Please call service-provider-registry-announce-upgrade.sh first." exit 1 fi else # Method doesn't exist (old contract without nextUpgrade) or call failed echo "nextUpgrade() method not found or call failed, using one-step mechanism (direct upgrade)" - echo "WARNING: This is the legacy upgrade path. For new deployments, use announce-planned-upgrade-registry.sh first." + echo "WARNING: This is the legacy upgrade path. For new deployments, use service-provider-registry-announce-upgrade.sh first." fi if [ -n "$NEW_VERSION" ]; then diff --git a/service_contracts/tools/deploy-session-key-registry.sh b/service_contracts/tools/session-key-registry-deploy.sh similarity index 100% rename from service_contracts/tools/deploy-session-key-registry.sh rename to service_contracts/tools/session-key-registry-deploy.sh diff --git a/service_contracts/tools/announce-planned-upgrade.sh b/service_contracts/tools/warm-storage-announce-upgrade.sh similarity index 96% rename from service_contracts/tools/announce-planned-upgrade.sh rename to service_contracts/tools/warm-storage-announce-upgrade.sh index 88d650b2..ef624731 100755 --- a/service_contracts/tools/announce-planned-upgrade.sh +++ b/service_contracts/tools/warm-storage-announce-upgrade.sh @@ -1,6 +1,6 @@ #!/bin/bash -# announce-planned-upgrade.sh: Completes a pending upgrade +# warm-storage-announce-upgrade.sh: Completes a pending upgrade # Required args: ETH_RPC_URL, FWSS_PROXY_ADDRESS, ETH_KEYSTORE, PASSWORD, NEW_FWSS_IMPLEMENTATION_ADDRESS, AFTER_EPOCH if [ -z "$ETH_RPC_URL" ]; then diff --git a/service_contracts/tools/deploy-all-warm-storage.sh b/service_contracts/tools/warm-storage-deploy-all.sh similarity index 97% rename from service_contracts/tools/deploy-all-warm-storage.sh rename to service_contracts/tools/warm-storage-deploy-all.sh index 590439a8..08ed63bf 100755 --- a/service_contracts/tools/deploy-all-warm-storage.sh +++ b/service_contracts/tools/warm-storage-deploy-all.sh @@ -1,5 +1,5 @@ #! /bin/bash -# deploy-all-warm-storage deploys the PDP verifier, FilecoinPayV1 contract, and Warm Storage service +# warm-storage-deploy-all.sh deploys the PDP verifier, FilecoinPayV1 contract, and Warm Storage service # Auto-detects network based on RPC chain ID and sets appropriate configuration # Assumption: KEYSTORE, PASSWORD, ETH_RPC_URL env vars are set to an appropriate eth keystore path and password # and to a valid ETH_RPC_URL for the target network. @@ -271,7 +271,7 @@ deploy_proxy_if_needed() { echo } -# Deploy session key registry if needed (uses ./deploy-session-key-registry.sh) +# Deploy session key registry if needed (uses ./session-key-registry-deploy.sh) deploy_session_key_registry_if_needed() { if [ -n "$SESSION_KEY_REGISTRY_ADDRESS" ]; then echo -e "${BOLD}SessionKeyRegistry${RESET}" @@ -287,7 +287,7 @@ deploy_session_key_registry_if_needed() { echo " 🧪 Using dummy address: $SESSION_KEY_REGISTRY_ADDRESS" else echo " 🔧 Using external deployment script..." - source "$SCRIPT_DIR/deploy-session-key-registry.sh" + source "$SCRIPT_DIR/session-key-registry-deploy.sh" NONCE=$(expr $NONCE + "1") echo " ✅ Deployed at: $SESSION_KEY_REGISTRY_ADDRESS" @@ -299,7 +299,7 @@ deploy_session_key_registry_if_needed() { echo } -# Deploy endorsements ProviderIdSet contract if needed (uses ./deploy-provider-id-set.sh) +# Deploy endorsements ProviderIdSet contract if needed (uses ./provider-id-set-deploy.sh) deploy_endorsements_if_needed() { if [ -n "$ENDORSEMENT_SET_ADDRESS" ]; then echo -e "${BOLD}Endorsements ProviderIdSet${RESET}" @@ -318,7 +318,7 @@ deploy_endorsements_if_needed() { AUTO_VERIFY_BEFORE=${AUTO_VERIFY:-true} AUTO_VERIFY=false # override so as to set last # This also updates deployments.json - source "$SCRIPT_DIR/deploy-provider-id-set.sh" + source "$SCRIPT_DIR/provider-id-set-deploy.sh" AUTO_VERIFY=$AUTO_VERIFY_BEFORE NONCE=$(expr $NONCE + "1") echo " ✅ Deployed at: $ENDORSEMENT_SET_ADDRESS" @@ -486,7 +486,7 @@ if [ "$DRY_RUN" = "true" ]; then echo " ✅ Deployment planned (dummy: $FWSS_VIEW_ADDRESS)" else echo " 🔧 Using external deployment script..." - source "$SCRIPT_DIR/deploy-warm-storage-view.sh" + source "$SCRIPT_DIR/warm-storage-deploy-view.sh" echo " ✅ Deployed at: $FWSS_VIEW_ADDRESS" NONCE=$(expr $NONCE + "1") @@ -503,7 +503,7 @@ if [ "$DRY_RUN" = "true" ]; then echo " 🔍 Would set view contract address on main contract (skipping in dry-run)" else echo " 🔧 Setting view address on FilecoinWarmStorageService..." - source "$SCRIPT_DIR/set-warm-storage-view.sh" + source "$SCRIPT_DIR/warm-storage-set-view.sh" echo " ✅ View address set" NONCE=$(expr $NONCE + "1") fi @@ -517,7 +517,7 @@ if [ "$DRY_RUN" = "true" ]; then echo "✅ Dry run completed successfully!" echo "🔍 All contract compilations and simulations passed" echo - echo "To perform actual deployment, run with: DRY_RUN=false ./tools/deploy-all-warm-storage.sh" + echo "To perform actual deployment, run with: DRY_RUN=false ./tools/warm-storage-deploy-all.sh" echo echo "# DRY-RUN SUMMARY ($NETWORK_NAME)" else diff --git a/service_contracts/tools/deploy-warm-storage-calibnet.sh b/service_contracts/tools/warm-storage-deploy-calibnet.sh similarity index 98% rename from service_contracts/tools/deploy-warm-storage-calibnet.sh rename to service_contracts/tools/warm-storage-deploy-calibnet.sh index 5c8a72ec..bd727599 100755 --- a/service_contracts/tools/deploy-warm-storage-calibnet.sh +++ b/service_contracts/tools/warm-storage-deploy-calibnet.sh @@ -1,5 +1,5 @@ #! /bin/bash -# deploy-warm-storage-calibnet deploys the Warm Storage service contract to calibration net +# warm-storage-deploy-calibnet.sh deploys the Warm Storage service contract to calibration net # Assumption: ETH_KEYSTORE, PASSWORD, ETH_RPC_URL env vars are set to an appropriate eth keystore path and password # and to a valid ETH_RPC_URL for the calibnet. # Assumption: forge, cast, jq are in the PATH @@ -109,7 +109,7 @@ if [ "$MAX_PROVING_PERIOD" -lt "$MIN_REQUIRED" ]; then echo " MAX_PROVING_PERIOD must be at least $MIN_REQUIRED (CHALLENGE_FINALITY + CHALLENGE_WINDOW_SIZE/2)" echo " To fix: Set MAX_PROVING_PERIOD to at least $MIN_REQUIRED" echo "" - echo " Example: MAX_PROVING_PERIOD=$MIN_REQUIRED CHALLENGE_WINDOW_SIZE=$CHALLENGE_WINDOW_SIZE ./deploy-warm-storage-calibnet.sh" + echo " Example: MAX_PROVING_PERIOD=$MIN_REQUIRED CHALLENGE_WINDOW_SIZE=$CHALLENGE_WINDOW_SIZE ./warm-storage-deploy-calibnet.sh" exit 1 fi diff --git a/service_contracts/tools/deploy-warm-storage-implementation-only.sh b/service_contracts/tools/warm-storage-deploy-implementation.sh similarity index 74% rename from service_contracts/tools/deploy-warm-storage-implementation-only.sh rename to service_contracts/tools/warm-storage-deploy-implementation.sh index 27df2a3b..47428450 100755 --- a/service_contracts/tools/deploy-warm-storage-implementation-only.sh +++ b/service_contracts/tools/warm-storage-deploy-implementation.sh @@ -1,5 +1,5 @@ #!/bin/bash -# deploy-warm-storage-implementation-only.sh - Deploy only FilecoinWarmStorageService implementation (no proxy) +# warm-storage-deploy-implementation.sh - Deploy only FilecoinWarmStorageService implementation (no proxy) # This allows updating an existing proxy to point to the new implementation # Assumption: ETH_KEYSTORE, PASSWORD, ETH_RPC_URL env vars are set # Assumption: forge, cast are in the PATH @@ -24,6 +24,10 @@ if [ -z "$CHAIN" ]; then fi fi +# Load deployments.json helpers and populate defaults if available +source "$(dirname "$0")/deployments.sh" +load_deployment_addresses "$CHAIN" + if [ -z "$ETH_KEYSTORE" ]; then echo "Error: ETH_KEYSTORE is not set" @@ -65,8 +69,34 @@ if [ -z "$SESSION_KEY_REGISTRY_ADDRESS" ]; then exit 1 fi -USDFC_TOKEN_ADDRESS="0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0" # USDFC token address on calibnet +# Set network-specific USDFC token address based on chain ID +case "$CHAIN" in + "31415926") + # Devnet requires explicit USDFC_TOKEN_ADDRESS (mock token) + if [ -z "$USDFC_TOKEN_ADDRESS" ]; then + echo "Error: USDFC_TOKEN_ADDRESS is not set (required for devnet)" + echo "Please set USDFC_TOKEN_ADDRESS to your deployed MockUSDFC address" + exit 1 + fi + ;; + "314159") + USDFC_TOKEN_ADDRESS="${USDFC_TOKEN_ADDRESS:-0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0}" # calibnet + ;; + "314") + USDFC_TOKEN_ADDRESS="${USDFC_TOKEN_ADDRESS:-0x80B98d3aa09ffff255c3ba4A241111Ff1262F045}" # mainnet + ;; + *) + echo "Error: Unsupported network" + echo " Supported networks:" + echo " 31415926 - Filecoin local development network" + echo " 314159 - Filecoin Calibration testnet" + echo " 314 - Filecoin mainnet" + echo " Detected chain ID: $CHAIN" + exit 1 + ;; +esac +SIGNATURE_LIB_DEPLOYED=false if [ -z "$SIGNATURE_VERIFICATION_LIB_ADDRESS" ]; then # Deploy SignatureVerificationLib first so we can link it into the implementation echo "Deploying SignatureVerificationLib..." @@ -77,6 +107,7 @@ if [ -z "$SIGNATURE_VERIFICATION_LIB_ADDRESS" ]; then exit 1 fi echo "SignatureVerificationLib deployed at: $SIGNATURE_VERIFICATION_LIB_ADDRESS" + SIGNATURE_LIB_DEPLOYED=true # Increment nonce for the next deployment NONCE=$((NONCE + 1)) else @@ -112,6 +143,13 @@ echo "SignatureVerificationLib deployed at: $SIGNATURE_VERIFICATION_LIB_ADDRESS" echo "FilecoinWarmStorageService Implementation deployed at: $FWSS_IMPLEMENTATION_ADDRESS" echo "" +# Persist deployment addresses + metadata +update_deployment_address "$CHAIN" "FWSS_IMPLEMENTATION_ADDRESS" "$FWSS_IMPLEMENTATION_ADDRESS" +if [ "$SIGNATURE_LIB_DEPLOYED" = "true" ]; then + update_deployment_address "$CHAIN" "SIGNATURE_VERIFICATION_LIB_ADDRESS" "$SIGNATURE_VERIFICATION_LIB_ADDRESS" +fi +update_deployment_metadata "$CHAIN" + # Automatic contract verification if [ "${AUTO_VERIFY:-true}" = "true" ]; then echo diff --git a/service_contracts/tools/deploy-warm-storage-view.sh b/service_contracts/tools/warm-storage-deploy-view.sh similarity index 83% rename from service_contracts/tools/deploy-warm-storage-view.sh rename to service_contracts/tools/warm-storage-deploy-view.sh index 5db5b7ef..f25abb6a 100755 --- a/service_contracts/tools/deploy-warm-storage-view.sh +++ b/service_contracts/tools/warm-storage-deploy-view.sh @@ -24,6 +24,11 @@ if [ -z "$CHAIN" ]; then fi fi +# Load deployments.json helpers and populate defaults if available +SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" +source "$SCRIPT_DIR/deployments.sh" +load_deployment_addresses "$CHAIN" + if [ -z "$FWSS_PROXY_ADDRESS" ]; then echo "Error: FWSS_PROXY_ADDRESS is not set" exit 1 @@ -47,6 +52,10 @@ export FWSS_VIEW_ADDRESS=$(forge create --password "$PASSWORD" --broadcast --non echo FilecoinWarmStorageServiceStateView deployed at $FWSS_VIEW_ADDRESS +# Persist deployment address + metadata +update_deployment_address "$CHAIN" "FWSS_VIEW_ADDRESS" "$FWSS_VIEW_ADDRESS" +update_deployment_metadata "$CHAIN" + # Automatic contract verification if [ "${AUTO_VERIFY:-true}" = "true" ]; then echo diff --git a/service_contracts/tools/upgrade.sh b/service_contracts/tools/warm-storage-execute-upgrade.sh similarity index 98% rename from service_contracts/tools/upgrade.sh rename to service_contracts/tools/warm-storage-execute-upgrade.sh index bb8ea307..35941ad0 100755 --- a/service_contracts/tools/upgrade.sh +++ b/service_contracts/tools/warm-storage-execute-upgrade.sh @@ -1,6 +1,6 @@ #!/bin/bash -# upgrade.sh: Completes a pending upgrade +# warm-storage-execute-upgrade.sh: Completes a pending upgrade # Required args: ETH_RPC_URL, FWSS_PROXY_ADDRESS, ETH_KEYSTORE, PASSWORD, NEW_WARM_STORAGE_IMPLEMENTATION_ADDRESS # Optional args: NEW_FWSS_VIEW_ADDRESS # Calculated if unset: CHAIN, FWSS_VIEW_ADDRESS diff --git a/service_contracts/tools/set-warm-storage-view.sh b/service_contracts/tools/warm-storage-set-view.sh similarity index 84% rename from service_contracts/tools/set-warm-storage-view.sh rename to service_contracts/tools/warm-storage-set-view.sh index 01c6efdf..35484d88 100755 --- a/service_contracts/tools/set-warm-storage-view.sh +++ b/service_contracts/tools/warm-storage-set-view.sh @@ -25,6 +25,11 @@ if [ -z "$CHAIN" ]; then fi fi +# Load deployments.json helpers and populate defaults if available +SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" +source "$SCRIPT_DIR/deployments.sh" +load_deployment_addresses "$CHAIN" + if [ -z "$FWSS_PROXY_ADDRESS" ]; then echo "Error: FWSS_PROXY_ADDRESS is not set" exit 1 @@ -55,6 +60,8 @@ TX_OUTPUT=$(cast send --password "$PASSWORD" --nonce $NONCE $FWSS_PROXY_ADDRESS if [ $? -eq 0 ]; then echo "View contract address set successfully" + update_deployment_address "$CHAIN" "FWSS_VIEW_ADDRESS" "$FWSS_VIEW_ADDRESS" + update_deployment_metadata "$CHAIN" else echo "Error: Failed to set view contract address" echo "$TX_OUTPUT"