diff --git a/service_contracts/test/FWSSDispatcherTransitionScripts.t.sol b/service_contracts/test/FWSSDispatcherTransitionScripts.t.sol index 864fcb086..242caff29 100644 --- a/service_contracts/test/FWSSDispatcherTransitionScripts.t.sol +++ b/service_contracts/test/FWSSDispatcherTransitionScripts.t.sol @@ -17,10 +17,12 @@ import {JosukeLedger} from "../script/lib/JosukeLedger.sol"; import {JosukeFacetSet} from "../script/lib/JosukeFacetSet.sol"; /// @dev Drives the transition scripts against the deployed mainnet v1.4.0 bytecode, at the mainnet proxy address -/// that deployments.json and josuke.json record for chain 314. +/// that the v1.4.0 deployments.json fixture records for chain 314. contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { uint256 private constant CHAIN = 314; string private constant V1_4_0_MAINNET = "test/fixtures/fwss-v1.4.0-mainnet.json"; + // deployments.json before the transition: the committed file gains dispatcher records once A1 runs on Mainnet. + string private constant SEED = "test/fixtures/deployments-v1.4.0.json"; string private constant SCRATCH = "out/FWSSDispatcherTransitionScripts"; string private constant DEPLOYMENTS = "out/FWSSDispatcherTransitionScripts/deployments.json"; string private constant SCRATCH_LEDGER = "out/FWSSDispatcherTransitionScripts/josuke.json"; @@ -152,7 +154,7 @@ contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { assertEq(vm.parseJsonString(json, ".314.metadata.commit"), GIT_COMMIT); _assertIsoTimestamp(vm.parseJsonString(json, ".314.metadata.deployed_at")); assertEq(vm.parseJsonString(json, ".314.metadata.fwss_version"), "1.4.0"); - string memory original = vm.readFile("deployments.json"); + string memory original = vm.readFile(SEED); assertEq( vm.parseJsonString(json, ".314.FWSS_PROXY_ADDRESS"), vm.parseJsonString(original, ".314.FWSS_PROXY_ADDRESS") ); @@ -236,7 +238,7 @@ contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { deploy.run(); // The commit is checked before anything is written. - assertEq(vm.readFile(DEPLOYMENTS), vm.readFile("deployments.json")); + assertEq(vm.readFile(DEPLOYMENTS), vm.readFile(SEED)); } function _deployDryRunRecordsNothing() internal { @@ -244,7 +246,7 @@ contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { vm.setEnv("GIT_COMMIT", ""); new DryRunDeploy().run(); - assertEq(vm.readFile(DEPLOYMENTS), vm.readFile("deployments.json")); + assertEq(vm.readFile(DEPLOYMENTS), vm.readFile(SEED)); } function _deployRefusesBroadcastWithoutWallet() internal { @@ -259,7 +261,7 @@ contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { dispatcherDeploy.run(); assertEq(vm.getNonce(DEFAULT_SENDER), nonce); - assertEq(vm.readFile(DEPLOYMENTS), vm.readFile("deployments.json")); + assertEq(vm.readFile(DEPLOYMENTS), vm.readFile(SEED)); } function _executeChecks() internal { @@ -424,7 +426,7 @@ contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { deploy.run(); assertEq(vm.getNonce(DEFAULT_SENDER), nonce); - assertEq(vm.readFile(DEPLOYMENTS), vm.readFile("deployments.json")); + assertEq(vm.readFile(DEPLOYMENTS), vm.readFile(SEED)); } function _deployHonoursPinned() internal { @@ -589,7 +591,7 @@ contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { function _dispatcherDeployRecordsDispatcher() internal { _writeLedger(proxy, address(0)); vm.setEnv("GIT_COMMIT", ""); - string memory original = vm.readFile("deployments.json"); + string memory original = vm.readFile(SEED); _dispatcherDeploy().run(); string memory json = vm.readFile(DEPLOYMENTS); @@ -667,7 +669,7 @@ contract FWSSDispatcherTransitionScriptsTest is Test, JosukeFacetSet { vm.revertToState(clean); vm.createDir(SCRATCH, true); - vm.copyFile("deployments.json", DEPLOYMENTS); + vm.copyFile(SEED, DEPLOYMENTS); _writeLedger(proxy, migration); vm.setEnv("DEPLOYMENTS_JSON_PATH", DEPLOYMENTS); diff --git a/service_contracts/test/FWSSJosukeLedger.t.sol b/service_contracts/test/FWSSJosukeLedger.t.sol index fa572c0e7..a823a6820 100644 --- a/service_contracts/test/FWSSJosukeLedger.t.sol +++ b/service_contracts/test/FWSSJosukeLedger.t.sol @@ -21,11 +21,8 @@ contract FWSSJosukeLedgerTest is Test, JosukeFacetSet { } function testLedgerAddresses() public view { - address mainnetAddress = _proxyAddress(MAINNET_INDEX); - address calibnetAddress = _proxyAddress(CALIBNET_INDEX); - // TODO: read these from deployments.json - assertEq(mainnetAddress, 0x8408502033C418E1bbC97cE9ac48E5528F371A9f); - assertEq(calibnetAddress, 0x02925630df557F957f70E112bA06e50965417CA0); + _assertLedgerAddress(MAINNET_INDEX, "314"); + _assertLedgerAddress(CALIBNET_INDEX, "314159"); } function testFacetsProvideDispatcherTransitionRoutes() public view { @@ -38,6 +35,17 @@ contract FWSSJosukeLedgerTest is Test, JosukeFacetSet { assertEq(_owners(facets, IERC8167.selectors.selector), 0); } + /// @dev From the dispatcher deploy until the transition executes, the ledger lists the proxy under the dispatcher. + function _assertLedgerAddress(uint256 index, string memory chain) private view { + string memory deployments = vm.readFile("deployments.json"); + address listed = _proxyAddress(index); + if (listed == vm.parseJsonAddress(deployments, string.concat(".", chain, ".FWSS_PROXY_ADDRESS"))) return; + + string memory dispatcherKey = string.concat(".", chain, ".FWSS_DISPATCHER_ADDRESS"); + assertTrue(vm.keyExistsJson(deployments, dispatcherKey), "ledger lists neither the proxy nor the dispatcher"); + assertEq(listed, vm.parseJsonAddress(deployments, dispatcherKey)); + } + function _owners(Facet[] memory facets, bytes4 selector) private pure returns (uint256 owners) { for (uint256 i; i < facets.length; ++i) { for (uint256 j; j < facets[i].selectors.length; ++j) { diff --git a/service_contracts/test/fixtures/deployments-v1.4.0.json b/service_contracts/test/fixtures/deployments-v1.4.0.json new file mode 100644 index 000000000..6ad77033c --- /dev/null +++ b/service_contracts/test/fixtures/deployments-v1.4.0.json @@ -0,0 +1,268 @@ +{ + "314": { + "metadata": { + "commit": "4cbbea4a4f7da51ad674a1d9d122c6e677acfbec", + "deployed_at": "2026-08-21T16:29:02Z", + "fwss_version": "1.4.0", + "pdp_version": "3.5.0" + }, + "FILECOIN_PAY_ADDRESS": "0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa", + "PDP_VERIFIER_PROXY_ADDRESS": "0xBADd0B92C1c71d02E7d520f64c0876538fa2557F", + "PDP_VERIFIER_IMPLEMENTATION_ADDRESS": "0x04D4CdB99E34D7e9a366E7eba7A835e034bc41b1", + "SESSION_KEY_REGISTRY_ADDRESS": "0x74FD50525A958aF5d484601E252271f9625231aB", + "SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS": "0xf55dDbf63F1b55c3F1D4FA7e339a68AB7b64A5eB", + "SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS": "0x1Bb676392272313598930FEf8D5B66FFECcE02F0", + "SIGNATURE_VERIFICATION_LIB_ADDRESS": "0x63904443e46cc9F3db35CB4Fe16c76E7ffCA453e", + "RAILS_LIB_ADDRESS": "0x54aC29E68B3C7C5F7F315CeF1a85ECA848B88a9b", + "FWSS_PROXY_ADDRESS": "0x8408502033C418E1bbC97cE9ac48E5528F371A9f", + "FWSS_IMPLEMENTATION_ADDRESS": "0xa874C7333c8e1689C33155AD7bEE980926086e21", + "FWSS_VIEW_ADDRESS": "0xcf184Ab1FD8D1a563054d30Aa1fFb08136998172", + "ENDORSEMENT_SET_ADDRESS": "0x59eFa2e8324E1551d46010d7B0B140eE2F5c726b", + "contracts": { + "SIGNATURE_VERIFICATION_LIB": { + "initcode_hash": "0xddcbe59b3ccf54d3f47c15dd4dda953aaf43d8b004811693aad2f1b3aaedffdc", + "artifact_contract": "src/lib/SignatureVerificationLib.sol:SignatureVerificationLib", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "RAILS_LIB": { + "initcode_hash": "0x67d94fb169bf3563a279a006eb936594c4bef7db610512fb05db306ecbc2b0f4", + "artifact_contract": "src/lib/Rails.sol:Rails", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "FWSS_IMPLEMENTATION": { + "initcode_hash": "0x0e6588ee8d338abe2d311709c7ebf75953d54e4f8cbb209d14692fbbe445fb92", + "artifact_contract": "src/FilecoinWarmStorageService.sol:FilecoinWarmStorageService", + "libraries": { + "src/lib/SignatureVerificationLib.sol:SignatureVerificationLib": "0x63904443e46cc9F3db35CB4Fe16c76E7ffCA453e", + "src/lib/Rails.sol:Rails": "0x54aC29E68B3C7C5F7F315CeF1a85ECA848B88a9b" + }, + "constructor_args": [ + "0xBADd0B92C1c71d02E7d520f64c0876538fa2557F", + "0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa", + "0x80B98d3aa09ffff255c3ba4A241111Ff1262F045", + "0x1D60d2F5960Af6341e842C539985FA297E10d6eA", + "0xf55dDbf63F1b55c3F1D4FA7e339a68AB7b64A5eB", + "0x74FD50525A958aF5d484601E252271f9625231aB", + "6" + ], + "pinned": true + }, + "SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION": { + "initcode_hash": "0xd0999d49e64712aa62f6139d4d98c4f1fab903734a713308bab3ef1a6397f542", + "artifact_contract": "src/ServiceProviderRegistry.sol:ServiceProviderRegistry", + "libraries": {}, + "constructor_args": [ + "3" + ], + "pinned": true + }, + "FWSS_PROXY": { + "initcode_hash": "0x3e2779ec9d23761f6466083f8c4f938dc2479a0d48037c1803b7b3592c58bd11", + "artifact_contract": "ERC1967Proxy.sol:ERC1967Proxy", + "libraries": {}, + "constructor_args": [ + "0xaF996097790c17D3C23Cc45A3035a29D293d1492", + "0x" + ], + "pinned": true + }, + "SERVICE_PROVIDER_REGISTRY_PROXY": { + "initcode_hash": "0x3e2779ec9d23761f6466083f8c4f938dc2479a0d48037c1803b7b3592c58bd11", + "artifact_contract": "ERC1967Proxy.sol:ERC1967Proxy", + "libraries": {}, + "constructor_args": [ + "0x01293CaFdE24DE89fF26d1A19Bfc4E36CBF74F9B", + "0x" + ], + "pinned": true + }, + "PDP_VERIFIER_PROXY": { + "initcode_hash": "0x3e2779ec9d23761f6466083f8c4f938dc2479a0d48037c1803b7b3592c58bd11", + "artifact_contract": "ERC1967/ERC1967Proxy.sol:ERC1967Proxy", + "libraries": {}, + "constructor_args": [ + "0xb41A97FEDD2D9497C639A643ec75E56CbCeDe8BA", + "0x" + ], + "pinned": true + }, + "FILECOIN_PAY": { + "initcode_hash": "0x7b6cdd8afb8e163aa5b38a930e337d43b1edd2c8e2475f9519cbaf7760af4352", + "artifact_contract": "lib/fws-payments/src/FilecoinPayV1.sol:FilecoinPayV1", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "SESSION_KEY_REGISTRY": { + "initcode_hash": "0x1ff3aaa830f4d7b24a89b622a740a03f0e5772a9725cb2272d57e6d07bf8df3b", + "artifact_contract": "lib/session-key-registry/src/SessionKeyRegistry.sol:SessionKeyRegistry", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "ENDORSEMENT_SET": { + "initcode_hash": "0x8c1e23722c664799417d2921502bd912e21417837519278a52b89d7e6275e5d6", + "artifact_contract": "src/ProviderIdSet.sol:ProviderIdSet", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "FWSS_VIEW": { + "initcode_hash": "0x3daadee16ed5f238f76fd90c913a10c9bb6db2aa6d99e5c02ee29b727b31222c", + "artifact_contract": "src/FilecoinWarmStorageServiceStateView.sol:FilecoinWarmStorageServiceStateView", + "libraries": {}, + "constructor_args": [ + "0x8408502033C418E1bbC97cE9ac48E5528F371A9f" + ], + "pinned": true + }, + "PDP_VERIFIER_IMPLEMENTATION": { + "initcode_hash": "0x43f5be1a5e546cfd7200624a02c53fdf46caa8150f16b0663a2e8c67d68e3dfe", + "artifact_contract": "lib/pdp/src/PDPVerifier.sol:PDPVerifier", + "libraries": {}, + "constructor_args": [ + "4", + "150" + ], + "pinned": true + } + } + }, + "314159": { + "metadata": { + "commit": "4cbbea4a4f7da51ad674a1d9d122c6e677acfbec", + "deployed_at": "2026-08-21T16:29:31Z", + "fwss_version": "1.4.0", + "pdp_version": "3.5.0" + }, + "FILECOIN_PAY_ADDRESS": "0x09a0fDc2723fAd1A7b8e3e00eE5DF73841df55a0", + "PDP_VERIFIER_PROXY_ADDRESS": "0x85e366Cf9DD2c0aE37E963d9556F5f4718d6417C", + "PDP_VERIFIER_IMPLEMENTATION_ADDRESS": "0x04D4CdB99E34D7e9a366E7eba7A835e034bc41b1", + "SESSION_KEY_REGISTRY_ADDRESS": "0x518411c2062E119Aaf7A8B12A2eDf9a939347655", + "SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS": "0x839e5c9988e4e9977d40708d0094103c0839Ac9D", + "SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION_ADDRESS": "0x0dF90c9a20b3f1E383c7196C06943565396c0956", + "SIGNATURE_VERIFICATION_LIB_ADDRESS": "0xa3c88DD6510f88747742482f0963Bb5aE670b022", + "RAILS_LIB_ADDRESS": "0xee2407847A4ccDdBB7eA6F188F19e998F0c92C2a", + "FWSS_PROXY_ADDRESS": "0x02925630df557F957f70E112bA06e50965417CA0", + "FWSS_IMPLEMENTATION_ADDRESS": "0xF5436B44354735588B20CA18F7D3e95a14FAcA10", + "FWSS_VIEW_ADDRESS": "0x1B68d64f01bAa42014B9774605867BF4eDC0320f", + "ENDORSEMENT_SET_ADDRESS": "0xAA2f7CfC7ecAc616EC9C1f6d700fAd19087FAC84", + "contracts": { + "FILECOIN_PAY": { + "initcode_hash": "0x7b6cdd8afb8e163aa5b38a930e337d43b1edd2c8e2475f9519cbaf7760af4352", + "artifact_contract": "lib/fws-payments/src/FilecoinPayV1.sol:FilecoinPayV1", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "SIGNATURE_VERIFICATION_LIB": { + "initcode_hash": "0xddcbe59b3ccf54d3f47c15dd4dda953aaf43d8b004811693aad2f1b3aaedffdc", + "artifact_contract": "src/lib/SignatureVerificationLib.sol:SignatureVerificationLib", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "SESSION_KEY_REGISTRY": { + "initcode_hash": "0xe86bf04ac10383355fc49667813f42688ed68a92e1d3414773cf34b2a1fd471f", + "artifact_contract": "lib/session-key-registry/src/SessionKeyRegistry.sol:SessionKeyRegistry", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "ENDORSEMENT_SET": { + "initcode_hash": "0x627e10a18cb5c30fdf02ef8886f04b3453b730d89114f28d4aeffd5663bc9d5d", + "artifact_contract": "src/ProviderIdSet.sol:ProviderIdSet", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "RAILS_LIB": { + "initcode_hash": "0x67d94fb169bf3563a279a006eb936594c4bef7db610512fb05db306ecbc2b0f4", + "artifact_contract": "src/lib/Rails.sol:Rails", + "libraries": {}, + "constructor_args": [], + "pinned": true + }, + "FWSS_VIEW": { + "initcode_hash": "0x3daadee16ed5f238f76fd90c913a10c9bb6db2aa6d99e5c02ee29b727b31222c", + "artifact_contract": "src/FilecoinWarmStorageServiceStateView.sol:FilecoinWarmStorageServiceStateView", + "libraries": {}, + "constructor_args": [ + "0x02925630df557F957f70E112bA06e50965417CA0" + ], + "pinned": true + }, + "FWSS_IMPLEMENTATION": { + "initcode_hash": "0x29ced5d5f582d5bac14ab7d090614adf1c0b67ead58f2aae585c73ffcff0846b", + "artifact_contract": "src/FilecoinWarmStorageService.sol:FilecoinWarmStorageService", + "libraries": { + "src/lib/SignatureVerificationLib.sol:SignatureVerificationLib": "0xa3c88DD6510f88747742482f0963Bb5aE670b022", + "src/lib/Rails.sol:Rails": "0xee2407847A4ccDdBB7eA6F188F19e998F0c92C2a" + }, + "constructor_args": [ + "0x85e366Cf9DD2c0aE37E963d9556F5f4718d6417C", + "0x09a0fDc2723fAd1A7b8e3e00eE5DF73841df55a0", + "0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0", + "0x1D60d2F5960Af6341e842C539985FA297E10d6eA", + "0x839e5c9988e4e9977d40708d0094103c0839Ac9D", + "0x518411c2062E119Aaf7A8B12A2eDf9a939347655", + "10" + ], + "pinned": true + }, + "PDP_VERIFIER_IMPLEMENTATION": { + "initcode_hash": "0x2ae11470a908b1d8a1a90573d98918e83668b8d350c1d3e2f6bfd6599cfae697", + "artifact_contract": "lib/pdp/src/PDPVerifier.sol:PDPVerifier", + "libraries": {}, + "constructor_args": [ + "4", + "10" + ], + "pinned": true + }, + "SERVICE_PROVIDER_REGISTRY_IMPLEMENTATION": { + "initcode_hash": "0xd0999d49e64712aa62f6139d4d98c4f1fab903734a713308bab3ef1a6397f542", + "artifact_contract": "src/ServiceProviderRegistry.sol:ServiceProviderRegistry", + "libraries": {}, + "constructor_args": [ + "3" + ], + "pinned": true + }, + "PDP_VERIFIER_PROXY": { + "initcode_hash": "0x460ab109982867b4e11bd02e84d83dfaef4aad379625b07382854b7199beab09", + "artifact_contract": "ERC1967Proxy.sol:MyERC1967Proxy", + "libraries": {}, + "constructor_args": [ + "0x2355Cb19BA1eFF51673562E1a5fc5eE292AF9D42", + "0x" + ], + "pinned": true + }, + "SERVICE_PROVIDER_REGISTRY_PROXY": { + "initcode_hash": "0x57cef7b58a4c5e3031590bb81e94f19c519bba5a3f2a5f26d58b76285a3a0868", + "artifact_contract": "ERC1967Proxy.sol:ERC1967Proxy", + "libraries": {}, + "constructor_args": [ + "0xb32Bb530638d20f1B59B40CDD2Ce4208430f7DE3", + "0x" + ], + "pinned": true + }, + "FWSS_PROXY": { + "initcode_hash": "0x57cef7b58a4c5e3031590bb81e94f19c519bba5a3f2a5f26d58b76285a3a0868", + "artifact_contract": "ERC1967Proxy.sol:ERC1967Proxy", + "libraries": {}, + "constructor_args": [ + "0x4BCc752555Bf08A5Bd9a4Ce467a12607277450bA", + "0x" + ], + "pinned": true + } + } + } +} diff --git a/service_contracts/tools/README.md b/service_contracts/tools/README.md index 066f3576e..80d47ddb9 100644 --- a/service_contracts/tools/README.md +++ b/service_contracts/tools/README.md @@ -236,7 +236,7 @@ josuke deploys the FWSS modules and the migration that routes their selectors, a ```bash forge script script/FWSSDispatcherDeploy.s.sol \ - --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" --broadcast + --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" --broadcast --skip-simulation (source tools/josuke.sh; josuke_move_entry "$FWSS_PROXY_ADDRESS" "$(jq -r ".\"$CHAIN\".FWSS_DISPATCHER_ADDRESS" deployments.json)") ``` @@ -246,7 +246,7 @@ josuke deploys the FWSS modules and the migration that routes their selectors, a ```bash GIT_COMMIT=$(git rev-parse HEAD) forge script script/FWSSDispatcherTransitionDeploy.s.sol \ --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" \ - --broadcast --verify --verifier blockscout --verifier-url https://filecoin.blockscout.com/api/ + --broadcast --skip-simulation --verify --verifier blockscout --verifier-url https://filecoin.blockscout.com/api/ ``` The script reads `FWSS_PROXY_ADDRESS` for the connected chain from `deployments.json`, the proposed migration for that proxy from `josuke.json`, listed under the proxy or the recorded dispatcher, and the proxy's current ERC-1967 implementation as the rollback target. It deploys the dispatcher from `lib/erc8167/out/Proxy.evm/Proxy.json` unless `FWSS_DISPATCHER_ADDRESS` is set or recorded, checks the dispatcher code hash, and deploys `FWSSDispatcherTransition` unless the recorded one has the same initcode and constructor arguments and the contract at that address pins the same previous implementation, dispatcher and migration; a record whose address has no code or other code is redeployed, and `pinned: true` on the `contracts` entry makes a mismatch an error instead. It refuses to run once the proxy already points at the dispatcher, or at a transition installed with empty upgrade data: only `migrate(migration)` or `abortTransition()` on the proxy moves it on from there. With `--broadcast` it records `FWSS_DISPATCHER_ADDRESS`, `FWSS_DISPATCHER_TRANSITION_ADDRESS`, the `contracts.FWSS_DISPATCHER_TRANSITION` metadata and the chain `metadata` in `deployments.json`, in the format `deployments.sh` reads; `GIT_COMMIT` fills `metadata.commit`. Unlike `deployments.sh`, it keeps `metadata.fwss_version`: the proxy still runs that version until the execute step. Without `--broadcast` it simulates everything and writes nothing. Commit `deployments.json`. @@ -261,17 +261,22 @@ josuke deploys the FWSS modules and the migration that routes their selectors, a ```bash forge script script/FWSSDispatcherTransitionExecute.s.sol \ - --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" --broadcast + --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" --broadcast --skip-simulation ``` - It checks that the announced plan is the transition and ready, that the transition pins the migration `josuke.json` proposes, that the migration code is unchanged since the transition was deployed, that the rollback target is the proxy's current implementation and that the dispatcher is the pinned one, then sends `upgradeToAndCall(transition, migrate(migration))`, which points the proxy at the dispatcher and runs the migration in one call. The script's post-check runs on the simulation, so confirm on chain with `cast implementation $FWSS_PROXY_ADDRESS` before `josuke accept`. With a Safe owner, `CALLDATA_ONLY=true` simulates the upgrade as the owner, then prints the transaction for the Safe UI instead of sending it. The script leaves `deployments.json` alone: it tracks UUPS implementations, and `josuke.json` records the routes from here on. + It checks that the announced plan is the transition and ready, that the transition pins the migration `josuke.json` proposes, that the migration code is unchanged since the transition was deployed, that the rollback target is the proxy's current implementation and that the dispatcher is the pinned one, then sends `upgradeToAndCall(transition, migrate(migration))`, which points the proxy at the dispatcher and runs the migration in one call. The script's post-check runs on the simulation, so confirm on chain with `cast implementation $FWSS_PROXY_ADDRESS` before `josuke accept`. With a Safe owner, `CALLDATA_ONLY=true` simulates the upgrade as the owner, then prints the transaction for the Safe UI instead of sending it. The call uses about 182M gas on FEVM, so a Safe signer must not cap the gas limit below that. The script leaves `deployments.json` alone: it tracks UUPS implementations, and `josuke.json` records the routes from here on. 6. List the proxy in `josuke.json` again with `josuke_move_entry "$FWSS_DISPATCHER_ADDRESS" "$FWSS_PROXY_ADDRESS"`, then `josuke accept` and commit `josuke.json`. After the transition, upgrades are josuke migrations: `josuke deploy`, then `announceMigration(migration, delay)` and `migrate(migration)` on the proxy, then `josuke accept`. -Script inputs, all optional: `FWSS_PROXY_ADDRESS`, `FWSS_DISPATCHER_ADDRESS` and `FWSS_DISPATCHER_TRANSITION_ADDRESS` override `deployments.json`; `DEPLOYMENTS_JSON_PATH` and `JOSUKE_LEDGER` point at other files, which `fs_permissions` in `foundry.toml` must allow (scratch copies go under `out/`); `CALLDATA_ONLY=true` prints Safe calldata. An empty variable counts as unset and a malformed one is an error, so a typo cannot fall back to a fresh deployment. The legacy `SKIP_LOAD_DEPLOYMENTS` and `SKIP_UPDATE_DEPLOYMENTS` flags are not read. Only `--broadcast` runs need a wallet. A dry run uses forge's default sender unless `--sender` is given, so pass the owner's address to simulate the execute step. forge 1.7.1 does not unlock the keystore for a dry run. `cast call` does, even with `--from`, and then sends the call from the keystore address (foundry-rs/foundry#17388, see #621): run josuke with `ETH_PASSWORD` set to a password file, give the legacy announce script the password itself in `PASSWORD` (it reads one only for a direct send, not with `CALLDATA_ONLY=true`), and keep `ETH_KEYSTORE` out of the environment for read-only `cast` steps. +Script inputs, all optional: `FWSS_PROXY_ADDRESS`, `FWSS_DISPATCHER_ADDRESS` and `FWSS_DISPATCHER_TRANSITION_ADDRESS` override `deployments.json`; `DEPLOYMENTS_JSON_PATH` and `JOSUKE_LEDGER` point at other files, which `fs_permissions` in `foundry.toml` must allow (scratch copies go under `out/`); `CALLDATA_ONLY=true` prints Safe calldata. +The legacy `SKIP_LOAD_DEPLOYMENTS` and `SKIP_UPDATE_DEPLOYMENTS` flags are not read. +Only `--broadcast` runs need a wallet. +A dry run uses forge's default sender unless `--sender` is given, so pass the owner's address to simulate the execute step. +forge 1.7.1 does not unlock the keystore for a dry run. +`cast call` does, even with `--from`, and then sends the call from the keystore address (foundry-rs/foundry#17388, see #621): run josuke with `ETH_PASSWORD` set to a password file, and keep `ETH_KEYSTORE` out of the environment for read-only `cast` steps. The announce script runs its own reads without `ETH_KEYSTORE`. -Broadcasting on Filecoin: `forge script` simulates against the latest state before sending, and the `fevm-foundry-kit` recommends `--skip-simulation` when that simulation misbehaves; receipts can arrive late on 30-second blocks, so add `--retries 10` and rerun with `--resume` when forge reports an empty receipt or a dropped transaction. The deploy script records `deployments.json` during the simulation, before forge sends anything, so a broadcast that fails (unfunded key, dropped transactions) leaves records whose addresses have no code. It refuses `--broadcast` without a wallet before recording anything: forge would sign as its default sender, whose addresses forge's own simulation occupies. `--resume` sends the saved transactions to exactly those addresses without rerunning the script; a plain rerun notices the missing code and deploys again, overwriting the records. Verification: `--verify --verifier blockscout --verifier-url ` runs with the broadcast (`https://filecoin-testnet.blockscout.com/api/` on Calibration); run `forge script ... --resume --verify --verifier sourcify` for Sourcify, and `tools/verify-contracts.sh`'s `filfox-verifier` for Filfox. The dispatcher is raw bytecode with no source to verify; forge skips it. +Broadcasting on Filecoin: always pass `--skip-simulation`. Without it forge sets each gas limit from its local EVM simulation, and FEVM gas is not EVM gas: Lotus rejects the deploys (`'GasLimit' field cannot be less than the cost of storing a message on chain`), and the execute transaction mines and reverts out of gas. With it, forge asks the node to estimate gas. Receipts can arrive late on 30-second blocks, so add `--retries 10` and rerun with `--resume` when forge reports an empty receipt or a dropped transaction. The deploy script records `deployments.json` during the simulation, before forge sends anything, so a broadcast that fails (unfunded key, dropped transactions) leaves records whose addresses have no code. It refuses `--broadcast` without a wallet before recording anything: forge would sign as its default sender, whose addresses forge's own simulation occupies. `--resume` sends the saved transactions to exactly those addresses without rerunning the script; a plain rerun notices the missing code and deploys again, overwriting the records. `--resume` only recovers transactions the node rejected or dropped. When a transaction mined and reverted, its nonce is spent and its address stays empty, so forge stops with `EOA nonce changed unexpectedly`; rerun without `--resume`. Keep `--skip-simulation` on `--resume` too, so forge asks the node for each gas limit again. Verification: `--verify --verifier blockscout --verifier-url ` runs with the broadcast (`https://filecoin-testnet.blockscout.com/api/` on Calibration); run `forge script ... --resume --verify --verifier sourcify` for Sourcify, and `tools/verify-contracts.sh`'s `filfox-verifier` for Filfox. The dispatcher is raw bytecode with no source to verify; forge skips it. `josuke check` runs offline against the working tree and reports what `josuke deploy` would do, including facets it cannot deploy. Do not run the transition on Calibration or Mainnet until `josuke check` passes. diff --git a/service_contracts/tools/UPGRADE-CHECKLIST.md b/service_contracts/tools/UPGRADE-CHECKLIST.md index 58403b459..c920707f5 100644 --- a/service_contracts/tools/UPGRADE-CHECKLIST.md +++ b/service_contracts/tools/UPGRADE-CHECKLIST.md @@ -525,7 +525,7 @@ Prerequisite: [Choose the Upgrade Path](#choose-the-upgrade-path) reports Path A ```bash forge script script/FWSSDispatcherDeploy.s.sol \ - --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" --broadcast + --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" --broadcast --skip-simulation export DISPATCHER=$(jq -r ".\"$CHAIN\".FWSS_DISPATCHER_ADDRESS" deployments.json) (source tools/josuke.sh; josuke_move_entry "$FWSS_PROXY_ADDRESS" "$DISPATCHER") @@ -534,9 +534,11 @@ josuke verify # rebuilds every facet from its recorded commit and checks it a GIT_COMMIT=$(git rev-parse HEAD) forge script script/FWSSDispatcherTransitionDeploy.s.sol \ --rpc-url "$ETH_RPC_URL" --keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" \ - --broadcast --verify # verifies on Sourcify, forge's default verifier + --broadcast --skip-simulation --verify # verifies on Sourcify, forge's default verifier ``` +Keep `--skip-simulation` on every `forge script --broadcast`. Without it forge sets each gas limit from its local EVM simulation, and FEVM gas is not EVM gas: Lotus rejects the two deploys (`'GasLimit' field cannot be less than the cost of storing a message on chain`), and a broadcast execute mines and reverts out of gas. With it, forge asks the node to estimate gas. + `FWSSDispatcherDeploy` deploys the dispatcher, or reuses the recorded one after checking its code hash, and records it in `deployments.json`. The transition deploy script reads the proxy and the dispatcher from `deployments.json`, the proposed migration from `josuke.json`, and the proxy's current implementation as the rollback target. It deploys `FWSSDispatcherTransition` and records it in `deployments.json`. See [`tools/README.md`](README.md#erc-8167-dispatcher-transition) for its full behavior and broadcast caveats. - [ ] Check the deployed transition against source. The transition's getters cannot prove what its code does, and it runs in the proxy's storage, so its runtime bytecode must match the frozen tag's build: @@ -600,6 +602,10 @@ CALLDATA_ONLY=true forge script script/FWSSDispatcherTransitionExecute.s.sol --r With `CALLDATA_ONLY=true` the script checks that the plan is the transition and ready, that the transition pins the migration `josuke.json` proposes and that migration's unchanged code, the proxy's current implementation as the rollback target, and the pinned dispatcher. It then simulates the upgrade as the proxy owner and prints the Safe transaction: `upgradeToAndCall(transition, migrate(migration))` on the proxy. That one call points the proxy at the dispatcher and runs the migration; if any part reverts, nothing changes. +The upgrade call uses about 182M gas on FEVM. Leave the gas limit to the Safe's estimate; a signer must not cap it below that. + +An EOA owner broadcasts the same call instead: replace `CALLDATA_ONLY=true` with `--keystore "$ETH_KEYSTORE" --password-file "$ETH_PASSWORD" --broadcast --skip-simulation`. + - [ ] Every signer [reproduces and decodes](#reproduce-and-decode-safe-calldata) the printed calldata, and decodes the inner `migrate(address)` call to confirm it names the pinned migration: ```bash diff --git a/service_contracts/tools/warm-storage-announce-upgrade.sh b/service_contracts/tools/warm-storage-announce-upgrade.sh index eb1f2d1a4..b657ac659 100755 --- a/service_contracts/tools/warm-storage-announce-upgrade.sh +++ b/service_contracts/tools/warm-storage-announce-upgrade.sh @@ -78,8 +78,10 @@ if [ "$UPGRADE_DELAY_EPOCHS" = "0" ]; then echo "The contract will enforce a minimum delay of one epoch" fi +# cast unlocks ETH_KEYSTORE even for `cast call`, prompting for a password and ignoring --from +# (foundry-rs/foundry#17388), so the reads run without it. ZERO_ADDRESS="0x0000000000000000000000000000000000000000" -if ! PROXY_OWNER=$(cast call --rpc-url "$ETH_RPC_URL" --from "$ZERO_ADDRESS" \ +if ! PROXY_OWNER=$(env -u ETH_KEYSTORE cast call --rpc-url "$ETH_RPC_URL" --from "$ZERO_ADDRESS" \ "$FWSS_PROXY_ADDRESS" "owner()(address)" 2>/dev/null); then echo "Error: Failed to read the FWSS proxy owner" exit 1 @@ -87,7 +89,7 @@ fi # Simulate from the owner before producing calldata. This catches invalid # implementation addresses and delay overflow. -if ! cast call --rpc-url "$ETH_RPC_URL" --from "$PROXY_OWNER" \ +if ! env -u ETH_KEYSTORE cast call --rpc-url "$ETH_RPC_URL" --from "$PROXY_OWNER" \ "$FWSS_PROXY_ADDRESS" "$CALL_SIGNATURE" "${CALL_ARGS[@]}" >/dev/null; then echo "Error: $CALL_SIGNATURE would revert against the current FWSS proxy" exit 1 diff --git a/service_contracts/tools/warm-storage-manage-approved-provider.sh b/service_contracts/tools/warm-storage-manage-approved-provider.sh index 83847fa93..889e887c1 100755 --- a/service_contracts/tools/warm-storage-manage-approved-provider.sh +++ b/service_contracts/tools/warm-storage-manage-approved-provider.sh @@ -18,7 +18,7 @@ # Optional: # CHAIN # FWSS_PROXY_ADDRESS -# FWSS_VIEW_ADDRESS +# FWSS_VIEW_ADDRESS - Defaults to FWSS_PROXY_ADDRESS # SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS # CALLDATA_ONLY=true - Print Safe calldata instead of sending/proposing # PROPOSE_TO_SAFE=true - Use Filecoin Safe Transaction Service instead of direct send @@ -163,10 +163,8 @@ if [ -z "${FWSS_PROXY_ADDRESS:-}" ]; then exit 1 fi -if [ -z "${FWSS_VIEW_ADDRESS:-}" ]; then - echo "Error: FWSS_VIEW_ADDRESS is not set (not found in deployments.json or environment)" - exit 1 -fi +# A modular FWSS records no view contract: the proxy serves the view methods itself. +FWSS_VIEW_ADDRESS="${FWSS_VIEW_ADDRESS:-$FWSS_PROXY_ADDRESS}" fwss_owner() { cast call --rpc-url "$ETH_RPC_URL" "$FWSS_PROXY_ADDRESS" "owner()(address)"