diff --git a/.env.example b/.env.example index dff7d7f..a25df21 100644 --- a/.env.example +++ b/.env.example @@ -1,20 +1,33 @@ -### FOR TESTING (USE ARB SEPOLIA AND BASE SEPOLIA, DO NOT CHANGE) -ARBITRUM_SEPOLIA_RPC=https://arb-sepolia.g.alchemy.com/v2/ALCHEMY_KEY -BASE_SEPOLIA_RPC=https://base-sepolia.g.alchemy.com/v2/ALCHEMY_KEY +# Copy to .env and fill in your secrets. +# Network configuration (RPC URL, chain ID, OSx addresses) is managed by +# just-foundry: +# just switch — activate a network (e.g. sepolia, mainnet, ...) +# just env — show resolved values and their sources -### `make test-e2e-fork` (mainnet forks). Leave empty to skip the fork suite. -### Note the fork suite falls back to RPC_URL below when MAINNET_RPC_URL is unset, -### so setting RPC_URL alone is enough to make `forge test` reach the network. -MAINNET_RPC_URL= -BASE_RPC_URL= +# Required for deployment (script/CreateRepo.sol and script/testnet/Test_Deploy.s.sol) +# DEPLOYER_KEY=0x -### Deployment -RPC_URL= -ETHERSCAN_API_KEY= -PRIVATE_KEY= +# Required for contract verification (when VERIFIER=etherscan) +# ETHERSCAN_API_KEY= -PLUGIN_ENS_SUBDOMAIN= # Optional (a random one is used if empty) -PLUGIN_REPO_MAINTAINER_ADDRESS= +# Optional: override the RPC endpoint of the active network +# RPC_URL= + +# Required for the fork suite (`just test-fork`). Both endpoints must be set — +# the suite forks two chains in the same process. Leave empty to skip. +# MAINNET_RPC_URL= +# BASE_RPC_URL= + +# Required for the testnet deploy script (script/testnet/Test_Deploy.s.sol) +# ARBITRUM_SEPOLIA_RPC=https://arb-sepolia.g.alchemy.com/v2/ALCHEMY_KEY +# BASE_SEPOLIA_RPC=https://base-sepolia.g.alchemy.com/v2/ALCHEMY_KEY + +# Plugin repo publication (used by `just deploy` / `just predeploy`). +# The maintainer defaults to MANAGEMENT_DAO_ADDRESS from the active +# just-foundry network config; override it here for a non-standard maintainer. +# MANAGEMENT_DAO_ADDRESS= RELEASE_METADATA_URI= BUILD_METADATA_URI= -PLUGIN_REPO_FACTORY_ADDRESS= \ No newline at end of file + +# Optional: a random subdomain is used if empty +# PLUGIN_ENS_SUBDOMAIN= diff --git a/.gitignore b/.gitignore index ed7039c..7d337b1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,12 @@ docs/ # Dotenv file .env +# just-foundry per-network overrides +.env.* -broadcast/ \ No newline at end of file +# just-foundry logs & coverage +logs/ +report/ +lcov.info* + +broadcast/ diff --git a/.gitmodules b/.gitmodules index 0843043..d3f3916 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,3 +24,6 @@ [submodule "lib/ens-contracts"] path = lib/ens-contracts url = https://github.com/ensdomains/ens-contracts +[submodule "lib/just-foundry"] + path = lib/just-foundry + url = https://github.com/aragon/just-foundry.git diff --git a/.vars.yaml b/.vars.yaml new file mode 100644 index 0000000..4ca0b3e --- /dev/null +++ b/.vars.yaml @@ -0,0 +1,21 @@ +# Secrets managed by vars (https://github.com/vars-cli/vars) +# Values are age-encrypted in your personal store — only key names are committed here. +# Usage: +# vars set DEPLOYER_KEY # store a value +# eval "$(vars resolve)" # load into current shell +# just deploy # justfile recipes call vars resolve automatically + +keys: + - DEPLOYER_KEY + - ETHERSCAN_API_KEY + - RPC_URL # optional override: the public RPC from the network file + # - REFUND_ADDRESS # optional: burner wallet refund target + +# Profiles are keyed by network name — just switch activates the matching profile. +# Store scoped values with: vars set /DEPLOYER_KEY + +profiles: + sepolia: + DEPLOYER_KEY: dev/DEPLOYER_KEY + zksync-sepolia: + DEPLOYER_KEY: dev/DEPLOYER_KEY diff --git a/Makefile b/Makefile deleted file mode 100644 index 0e93aaa..0000000 --- a/Makefile +++ /dev/null @@ -1,37 +0,0 @@ --include .env -export - -.PHONY: test test-e2e test-e2e-fork - -test: ## Run the whole suite; the fork tests skip themselves without RPCs - forge test - -test-e2e: ## Run the end-to-end suites; the fork tests skip themselves without RPCs - forge test --match-path 'test/e2e/*.t.sol' - -test-e2e-fork: ## Run the end-to-end suite against real CCIP Routers - @if [ -z "$$MAINNET_RPC_URL" ] || [ -z "$$BASE_RPC_URL" ]; then \ - echo "MAINNET_RPC_URL and BASE_RPC_URL must be set (in .env or the environment);"; \ - echo "the suite skips every test without them."; \ - exit 1; \ - fi - forge test --match-path 'test/e2e/fork/*' - -predeploy: ## Simulate a protocol deployment - @echo "Simulating the deployment" - forge script CreateRepo --rpc-url $(RPC_URL) - -deploy: ## Deploy and verify the protocol - forge script CreateRepo \ - --rpc-url $(RPC_URL) \ - --broadcast \ - --verify \ - --etherscan-api-key $(ETHERSCAN_API_KEY) - -verify: ## Verify all contracts from the last broadcast - forge script CreateRepo \ - --rpc-url $(RPC_URL) \ - --private-key $(PRIVATE_KEY) \ - --verify \ - --etherscan-api-key $(ETHERSCAN_API_KEY) \ - --resume diff --git a/README.md b/README.md index 07ac2d4..1c45158 100644 --- a/README.md +++ b/README.md @@ -66,23 +66,32 @@ chain. See [Same Chain Delivery](./specs/SPEC.md#same-chain-delivery). ## Usage -Requires [Foundry](https://book.getfoundry.sh/getting-started/installation). +Requires [Foundry](https://book.getfoundry.sh/getting-started/installation) and +[just](https://github.com/casey/just). Task running and per-network env are +handled by [just-foundry](https://github.com/aragon/just-foundry), vendored as a +submodule under `lib/just-foundry`. + +```shell +git submodule update --init +just init sepolia # or mainnet, base, ... — activates the network config +just help # list every recipe +``` ```shell forge build forge fmt -make test # the whole suite -make test-e2e # the end-to-end suites -make test-e2e-fork # end-to-end against real CCIP routers; requires RPC endpoints +just test # the whole suite +just test-e2e # the end-to-end suites +just test-fork # end-to-end against real CCIP routers; needs RPC endpoints ``` -The fork tests are not excluded by the first two targets — they skip themselves +The fork tests are not excluded by the first two targets: they skip themselves unless `MAINNET_RPC_URL` (or `RPC_URL`) and `BASE_RPC_URL` are set, in which case -they will reach the network. +they reach the network. `just test-fork` refuses to run without both. Unit suites live in `test/unit/`, mostly one file per function, plus a few cross-cutting suites. The end-to-end suites carry a message the whole way through -both stacks — see [test/e2e/README.md](./test/e2e/README.md). +both stacks: see [test/e2e/README.md](./test/e2e/README.md). ## Deployment @@ -90,14 +99,22 @@ both stacks — see [test/e2e/README.md](./test/e2e/README.md). install it. `script/CreateRepo.sol` deploys the implementation, the setup contract and the repo in one go. -Copy `.env.example` to `.env` and fill it in, then simulate and broadcast: +Copy `.env.example` to `.env` and fill it in, activate the target network, then +simulate and broadcast: ```shell -make predeploy # simulate -make deploy # broadcast and verify -make verify # re-verify from the last broadcast +just switch sepolia # or any other supported network +just env # verify the resolved env (RPC, factory addresses, ...) +just predeploy # simulate +just deploy # broadcast and verify +just verify # re-verify from the last broadcast ``` +`DEPLOYER_KEY` and `ETHERSCAN_API_KEY` come from `.env` (or from +[vars](https://github.com/vars-cli/vars)); every other network parameter, +including `PLUGIN_REPO_FACTORY_ADDRESS`, is supplied by the active network +config under `lib/just-foundry/networks/`. + Installing the plugin on a DAO goes through the OSx `PluginSetupProcessor`, pointing at that repo. Installation parameters are `(executor, guardian, minFailedMessageGas)` — see `CrossChainControllerSetup.encodeInstallationParameters`. Passing `address(0)` as the diff --git a/justfile b/justfile new file mode 100644 index 0000000..47d13c4 --- /dev/null +++ b/justfile @@ -0,0 +1,35 @@ +default: help + +import 'lib/just-foundry/justfile' + +DEPLOY_SCRIPT := "script/CreateRepo.sol:CreateRepo" + +# End-to-end suites (test/e2e/*.t.sol). Fork tests are included and self-skip +# when MAINNET_RPC_URL / BASE_RPC_URL are unset. +[group('test')] +test-e2e *args: + #!/usr/bin/env bash + set -euo pipefail + source {{ JUST_LIB }} && env_load + FORGE=$(just resolve-forge) || exit 1 + BUILD_PARAMS=$(just resolve-build-params) || exit 1 + ETHERSCAN_API_KEY="" $FORGE test $BUILD_PARAMS -vvv \ + --match-path 'test/e2e/*.t.sol' {{ args }} + +# End-to-end fork suite against real CCIP routers. Requires BOTH +# MAINNET_RPC_URL and BASE_RPC_URL — the suite forks two chains in the same +# process, so a single RPC won't do. Shadows the inherited `test-fork`. +[group('test')] +test-fork *args: + #!/usr/bin/env bash + set -euo pipefail + source {{ JUST_LIB }} && env_load + if [ -z "${MAINNET_RPC_URL:-}" ] || [ -z "${BASE_RPC_URL:-}" ]; then + echo "MAINNET_RPC_URL and BASE_RPC_URL must be set (in .env or the environment);" + echo "the suite skips every test without them." + exit 1 + fi + FORGE=$(just resolve-forge) || exit 1 + BUILD_PARAMS=$(just resolve-build-params) || exit 1 + $FORGE test $BUILD_PARAMS -vvv \ + --match-path 'test/e2e/fork/*' {{ args }} diff --git a/lib/just-foundry b/lib/just-foundry new file mode 160000 index 0000000..3b8e5cb --- /dev/null +++ b/lib/just-foundry @@ -0,0 +1 @@ +Subproject commit 3b8e5cb1dfe3440b8eddd4431d93613cbddf5bce diff --git a/script/CreateRepo.sol b/script/CreateRepo.sol index 4da304e..797e465 100644 --- a/script/CreateRepo.sol +++ b/script/CreateRepo.sol @@ -16,7 +16,7 @@ import { CrossChainController } from "@src/CrossChainController.sol"; contract CreateRepo is Script { address deployer; string pluginEnsSubdomain; - address pluginRepoMaintainerAddress; + address managementDao; PluginRepoFactory pluginRepoFactory; bytes releaseMetadataUri; bytes buildMetadataUri; @@ -26,14 +26,14 @@ contract CreateRepo is Script { address pluginSetup; modifier broadcast() { - uint256 privKey = vm.envUint("PRIVATE_KEY"); + uint256 privKey = vm.envUint("DEPLOYER_KEY"); vm.startBroadcast(privKey); deployer = vm.addr(privKey); - console.log("General"); + console.log("General:"); console.log("- Deploying from: ", deployer); console.log("- Chain ID: ", block.chainid); - console.log(""); + console.log(); _; @@ -56,8 +56,11 @@ contract CreateRepo is Script { pluginEnsSubdomain = string.concat("cross-chain-controller", vm.toString(block.timestamp)); } - pluginRepoMaintainerAddress = vm.envAddress("PLUGIN_REPO_MAINTAINER_ADDRESS"); - vm.label(pluginRepoMaintainerAddress, "Maintainer"); + // The Aragon management DAO becomes the repo maintainer. + // `MANAGEMENT_DAO_ADDRESS` is supplied by the active just-foundry + // network config; override in `.env` for a non-standard maintainer. + managementDao = vm.envAddress("MANAGEMENT_DAO_ADDRESS"); + vm.label(managementDao, "Maintainer"); releaseMetadataUri = vm.envOr("RELEASE_METADATA_URI", bytes(" ")); buildMetadataUri = vm.envOr("BUILD_METADATA_URI", bytes(" ")); @@ -68,13 +71,14 @@ contract CreateRepo is Script { pluginSetup = address(new CrossChainControllerSetup(address(new CrossChainController()))); myPluginRepo = pluginRepoFactory.createPluginRepoWithFirstVersion( - pluginEnsSubdomain, pluginSetup, pluginRepoMaintainerAddress, releaseMetadataUri, buildMetadataUri + pluginEnsSubdomain, pluginSetup, managementDao, releaseMetadataUri, buildMetadataUri ); - console.log("PluginRepo: ", address(myPluginRepo)); - console.log("CrossChainControllerSetup: ", address(pluginSetup)); - console.log("CrossChainController impl: ", IPluginSetup(pluginSetup).implementation()); - console.log("Maintainer: ", pluginRepoMaintainerAddress); - console.log("Subdomain: ", pluginEnsSubdomain); + console.log("CrossChainController plugin:"); + console.log("- PluginRepo: ", address(myPluginRepo)); + console.log("- PluginSetup: ", pluginSetup); + console.log("- Implementation: ", IPluginSetup(pluginSetup).implementation()); + console.log("- Maintainer (Management DAO): ", managementDao); + console.log("- ENS subdomain: ", pluginEnsSubdomain); } } diff --git a/script/testnet/Test_Deploy.s.sol b/script/testnet/Test_Deploy.s.sol index 3b4ac14..a52628c 100644 --- a/script/testnet/Test_Deploy.s.sol +++ b/script/testnet/Test_Deploy.s.sol @@ -25,7 +25,7 @@ import { ChainIdRegistry } from "@src/registry/ChainIdRegistry.sol"; /// Bridge to Arb Sepolia: https://bridge.arbitrum.io/?sourceChain=sepolia&destinationChain=arbitrum-sepolia /// Bridge to Base Sepolia: https://testnets.superbridge.app/base-sepolia /// STEP 2: Include these in .env: -/// PRIVATE_KEY= +/// DEPLOYER_KEY= /// ARBITRUM_SEPOLIA_RPC= /// BASE_SEPOLIA_RPC= /// STEP 3: run the following command: @@ -87,7 +87,7 @@ contract CounterTarget { /// submitted. /// /// ENV -/// PRIVATE_KEY deployer key, funded with testnet ETH on BOTH chains +/// DEPLOYER_KEY deployer key, funded with testnet ETH on BOTH chains /// ARBITRUM_SEPOLIA_RPC Arbitrum Sepolia RPC url /// BASE_SEPOLIA_RPC Base Sepolia RPC url /// FUND_AMOUNT_WEI optional; native pre-funding per controller @@ -135,7 +135,7 @@ contract Test_Deploy is Script { address internal deployer; function run() external { - deployerKey = vm.envUint("PRIVATE_KEY"); + deployerKey = vm.envUint("DEPLOYER_KEY"); deployer = vm.addr(deployerKey); uint256 fundAmount = vm.envOr("FUND_AMOUNT_WEI", uint256(0.03 ether)); diff --git a/test/e2e/README.md b/test/e2e/README.md index be0eb84..9820c95 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -37,12 +37,12 @@ makes them fail. Every suite here goes through a real OSx `DAO`, except ## Running ```bash -make test-e2e # the end-to-end suites -make test-e2e-fork # needs MAINNET_RPC_URL + BASE_RPC_URL +just test-e2e # the end-to-end suites +just test-fork # needs MAINNET_RPC_URL + BASE_RPC_URL ``` `--match-path 'test/e2e/*.t.sol'` does not exclude `fork/` — the glob crosses -directory separators — so `make test-e2e` and a plain `forge test` both select +directory separators — so `just test-e2e` and a plain `forge test` both select the fork suite. It `vm.skip`s every test when the RPC endpoints are absent, which is why CI is unaffected; with endpoints configured it will reach the network. Note it falls back to `RPC_URL` when `MAINNET_RPC_URL` is unset.