diff --git a/.github/workflows/validate-common-scripts.yml b/.github/workflows/validate-common-scripts.yml index 817b67b2..479267e8 100644 --- a/.github/workflows/validate-common-scripts.yml +++ b/.github/workflows/validate-common-scripts.yml @@ -41,6 +41,9 @@ jobs: with: experimental: true + - name: Check active EVM Make dispatcher + run: bash active/evm/make/check-dispatch.sh + # ── Dependencies ───────────────────────────────────────────────── # Install Foundry deps into the shared active/evm project. PROJECT_DIR # points forge install at active/evm; BASE_CONTRACTS_COMMIT is supplied diff --git a/.gitignore b/.gitignore index 3a97aff2..7debb48f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ yarn-error.log .DS_Store signer-tool/ + +# Active EVM task-local Forge outputs +active/evm/tasks/**/run-latest.json +active/evm/tasks/**/dry-run/ diff --git a/README.md b/README.md index 75b9814b..fdf4ff7d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ This repo contains execution code and artifacts related to Base contract deployments, upgrades, and calls. For actual contract implementations, see [base/contracts](https://github.com/base/contracts). -This repo is structured with each network having a high-level directory which contains subdirectories of any "tasks" (contract deployments/calls) that have happened for that network. +Current EVM tasks and shared tooling live under `active/evm/`. Historical tasks +are grouped by network under `archive/legacy/`. @@ -33,7 +34,8 @@ This repo is structured with each network having a high-level directory which co All required tooling (Foundry, Node.js, Bun, Go) is pinned in [`mise.toml`](mise.toml) so that every contributor — and especially every signer — runs identical versions. This eliminates a class of bugs where domain separators, build artifacts, or generated signatures differ between machines. -**Signers and facilitators don't need to install anything.** `make sign-task` (and `make deps`, `make execute`, etc.) automatically: +`make sign-task` (and `make deps`, `make execute`, etc.) bootstraps the pinned +toolchain automatically: 1. Installs [`mise`](https://mise.jdx.dev) to `~/.local/bin/mise` if it's not already present, using the vendored installer at [`scripts/install-mise.sh`](scripts/install-mise.sh). 2. Trusts the repo's `mise.toml` and runs `mise install` to fetch the pinned `foundry`, `node`, `bun`, and `go` versions. @@ -70,162 +72,39 @@ echo 'eval "$(mise activate bash)"' >> ~/.bashrc This is purely a convenience for task authors — `make` targets work correctly without it. -### Running a task +### Active EVM tasks -To execute a new task, run one of the following commands (depending on the type of change you're making): - -- For gas increase tasks: `make setup-gas-increase network=` -- For combined gas, elasticity, and DA footprint gas scalar tasks: `make setup-gas-and-elasticity-increase network=` -- For safe management tasks: `make setup-safe-management network=` -- For funding tasks: `make setup-funding network=` -- For updating the partner threshold in Base Bridge: `make setup-bridge-partner-threshold network=` -- For pausing / un-pausing Base Bridge: `make setup-bridge-pause network=` -- For pausing SuperchainConfig: `make setup-superchain-config-pause network=` - -Each `setup-*` command also creates a matching `/signatures//` directory for [task origin signing](#task-origin-signing). The parent `signatures/` directory is created automatically via `mkdir -p` for networks that do not yet have one. - -Next, `cd` into the directory that was created for you and follow the steps listed below for the relevant template. - -Please note, for some older tasks (that have not yet been adapted to use the signer tool) you will need to manually create validation file(s) for your task as they are bespoke to each task and therefore not created automatically as a part of the templates. We use one validation Markdown file per multisig involved in the task, so if there's only one multisig involved in your task, then you can simply create a `VALIDATION.md` file at the root of your task containing the validation instructions, while if there are multiple multisigs involved in the task, then create a `validations/` sub-directory at the root of your task containing the corresponding validation Markdown files. If you need examples to work from, you can browse through similar past tasks in this repo and adapt them to your specific task. Also, please note that we have tooling to generate these files (like the `task-signer-tool`) which removes the manual aspect of creating these validation files, we will soon update these instructions to reflect how this process can be automated. - -## Network configuration - -Each network directory (`mainnet/`, `sepolia/`, `sepolia-alpha/`, `zeronet/`) contains a `.env` file that defines all contract addresses and network metadata for that chain. These variables are automatically available to every task via the `include ../.env` directive in each task's Makefile, so there is no need to manually load addresses in individual tasks or templates. - -The network `.env` files contain: - -- **Network metadata** — `NETWORK`, `L1_RPC_URL`, `L2_RPC_URL`, `L1_CHAIN_ID`, `L2_CHAIN_ID`, `LEDGER_ACCOUNT` -- **Admin addresses** — multisig addresses, proposer, challenger, batch sender, etc. -- **L1 contract addresses** — proxy admin, bridges, dispute game factories, system config, etc. -- **L2 contract addresses** — fee vaults, cross-domain messenger, standard bridge, etc. - -All address variables are prefixed with `export` so they are available to child shell processes (Forge scripts, shell commands, etc.). Foundry scripts can access them via `vm.envAddress("VARIABLE_NAME")`. - -> **Note:** If you need to add or update a contract address, edit the corresponding `{network}/.env` file directly. Do not create per-task address definitions unless they are truly task-specific. - -## Directory structure - -Active EVM tasks live under `active/evm/`, which is a single shared Foundry -project rather than a standalone project per task. A single `active/evm/Makefile` -selects the active task via `TASK_ID` / `TASK_NETWORK`, reusable operation -scripts are shared across tasks under `script/common//`, and each task -directory holds only its own config, docs, and (per-network) validations and -signatures: - -```text -active/evm/ -├── Makefile # shared; selects the task via TASK_ID / TASK_NETWORK -├── foundry.toml # shared Foundry config (base-contracts v8.2.1) -├── script/ -│ └── common/ # reusable scripts, shared across tasks -│ └── / # bridge, funding, gas, ownership, safe, superchain, verifier-update -└── tasks/ - └── / - ├── FACILITATOR.md - ├── config/ - │ └── / - │ ├── .env # task inputs + BASE_CONTRACTS_COMMIT + RECORD_STATE_DIFF - │ ├── network.env # RPC, chain ids, Safe/contract addresses - │ ├── README.md # status + description (parsed by the signer tool) - │ └── validations/ # generated per-signer validation JSON - └── signatures/ - └── / # task-origin signatures (when required) -``` - -Task commands run from `active/evm`, selecting the task by `TASK_ID` / -`TASK_NETWORK` (both default to the current task in the shared `Makefile`), e.g. -`TASK_ID= TASK_NETWORK= make gen-validation-cb`. The shared -Makefile runs Forge from `active/evm` using the shared `foundry.toml` and `lib/`, -while task-specific files are read from `tasks//config//`. -Reusable scripts are documented in [`active/evm/script/common/README.md`](active/evm/script/common/README.md); -put a script under `script/common//` when it will be reused across -tasks, and keep one-off task glue out of `common/`. - -The shared `active/evm/Makefile` selects a task (via `TASK_ID` / `TASK_NETWORK`) -and sources that task's `.env` for `BASE_CONTRACTS_COMMIT`. To install -dependencies for the shared project without selecting a task (e.g. to build the -common scripts locally), invoke the root Makefile directly with a `PROJECT_DIR` -override and an explicit `BASE_CONTRACTS_COMMIT`: +[`active/evm/tasks/README.md`](active/evm/tasks/README.md) is the task-authoring +and execution guide. [`active/evm/script/common/README.md`](active/evm/script/common/README.md) +lists the reusable Solidity operations. ```bash -make deps PROJECT_DIR="$PWD/active/evm" BASE_CONTRACTS_COMMIT= +make -C active/evm TASK_ID= TASK_NETWORK= gen-validation ``` +Reusable workflows use the multisig helpers documented in [`Multisig.mk`](Multisig.mk). +Signing is handled by the [task-signing-tool](https://github.com/base/task-signing-tool). + ### Legacy tasks -Each legacy task (under a network directory, or `archive/legacy/`) has a directory structure similar to the following: +Each task under `archive/legacy//` has a structure similar to: - **records/** Foundry will autogenerate files here from running commands - **script/** place to store any one-off Foundry scripts - **src/** place to store any one-off smart contracts (long-lived contracts should go in [base/contracts](https://github.com/base/contracts)) - **.env** place to store task-specific environment variables (contract addresses are inherited from the network-level `.env`) -## CI — Common Script Validation - -A GitHub Actions workflow automatically validates the shared `active/evm` scripts on every pull request and on pushes to `main`. - -**What CI checks:** - -1. **Solidity formatting** — `forge fmt --check script/` ensures formatting consistency. -2. **Compilation** — `forge build` verifies that imports resolve, types are correct, and all dependencies are present. - -**How it works:** - -- All tooling (Foundry, Node, Bun, Go) is installed by the [`jdx/mise-action`](https://github.com/jdx/mise-action) GitHub Action using the versions pinned in [`mise.toml`](mise.toml), so CI matches local signer environments. -- `make deps PROJECT_DIR="$PWD/active/evm"` installs dependencies into the shared `active/evm` Foundry project. `BASE_CONTRACTS_COMMIT` is supplied via the workflow's `env` block, since the shared project has no task `.env` of its own. -- `forge fmt --check` and `forge build` then run in `active/evm` against the shared `foundry.toml` and `script/common/`. - -**What CI does NOT do:** - -- Does not run `forge script` (requires RPC URLs, env vars, and hardware wallets). -- Does not run `forge test` (no test files exist for these scripts). -- Does not run signing or execution targets (they depend on network state and hardware wallets). - -> See [`.github/workflows/validate-common-scripts.yml`](.github/workflows/validate-common-scripts.yml) for the full workflow definition. - -## Multisig macro convention - -All task templates use global macros defined in [`Multisig.mk`](Multisig.mk) for multisig operations: - -| Macro | Purpose | Key arguments | -| ------------------ | --------------------------------------------------------------- | --------------------------------------------------------- | -| `MULTISIG_APPROVE` | Approve a transaction (nested safe hierarchy) | `(address_list, signatures)` | -| `MULTISIG_EXECUTE` | Execute an approved transaction on-chain | `(signatures)` | -| `GEN_VALIDATION` | Generate a validation JSON file for signers via the signer-tool | `(script_name, safe_addr, sender, output_file, env_vars)` | - -Two helper macros are also available for tasks that need nonce offset calculations or address manipulation: - -| Macro | Purpose | Key arguments | -| ------------ | ---------------------------------------------------------- | ---------------- | -| `GET_NONCE` | Fetch the current nonce of a Safe contract on-chain | `(safe_address)` | -| `ADDR_UPPER` | Convert an address to uppercase (for env var construction) | `(address)` | - -Signing is handled externally by the [task-signing-tool](https://github.com/base/task-signing-tool). - -Every template Makefile should include `Multisig.mk` and define at least two variables for the macros to work: - -```makefile -include ../../Makefile -include ../../Multisig.mk -include ../.env -include .env - -RPC_URL = $(L1_RPC_URL) # or $(L2_RPC_URL) -SCRIPT_NAME = MyScript # class name or .sol file path -``` - -Templates that generate validation files should use `GEN_VALIDATION` with the `deps-signer-tool` prerequisite (which checks out and installs the signer-tool): - -```makefile -gen-validation: validate-config deps-signer-tool - $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer.json,) -``` +## CI -Templates should use these macros rather than inline `forge script` / `eip712sign` / `bun run` invocations. The known exceptions are the incident-response pause templates, which pre-sign 20 future nonces in a loop using inline `eip712sign`; only their `execute-*` targets use `MULTISIG_EXECUTE`. +CI formats and builds the shared Solidity and checks the Make dispatcher. It +does not sign or execute tasks. See +[`.github/workflows/validate-common-scripts.yml`](.github/workflows/validate-common-scripts.yml). ## Task origin signing -The root Makefile provides three targets for generating cryptographic attestations (sigstore bundles) that prove who created and facilitated a task. These are inherited by all task Makefiles via `include ../../Makefile`. +The root Makefile provides three targets for generating cryptographic +attestations that prove who created and facilitated a task. Legacy task +Makefiles and the active EVM dispatcher inherit these targets. | Target | Purpose | | ------------------------------- | ----------------------------------------------- | @@ -233,119 +112,6 @@ The root Makefile provides three targets for generating cryptographic attestatio | `make sign-as-base-facilitator` | Attest Base team facilitation | | `make sign-as-sc-facilitator` | Attest Security Council facilitation | -Signatures are stored in `/signatures//`, where `` is auto-derived from the task directory name. This directory is created automatically when you run any `setup-*` target (in both the root and Solana Makefiles), so it is ready for the signing tool when you invoke one of the targets below. Two variables control this behavior and can be overridden in a task's Makefile if the defaults are not appropriate: - -| Variable | Default | Description | -| --------------- | ------------------------------------------ | ------------------------------------- | -| `TASK_NAME` | `$(notdir $(CURDIR))` (directory basename) | Name used to locate signature dir | -| `SIGNATURE_DIR` | `$(CURDIR)/../signatures/$(TASK_NAME)` | Directory where signatures are stored | - -All three targets depend on `deps-signer-tool`, which checks out and installs the [task-signing-tool](https://github.com/base/task-signing-tool) automatically. - -For `active/evm` tasks, the shared Makefile overrides `TASK_ORIGIN_DIR` and `SIGNATURE_DIR`: the signer tool signs over the `active/evm/tasks//config/` directory (`TASK_ORIGIN_DIR`), while the signatures themselves are written to `active/evm/tasks//signatures//` (`SIGNATURE_DIR`) — outside the signed directory, so generating signatures does not change the signed payload. A task may opt out of task-origin validation entirely by setting `skipTaskOriginValidation: true` at the root of each validation file (e.g. non-production networks such as zeronet). - -## Using the gas limit increase template - -This template is increasing the throughput on Base Chain. - -1. Ensure you have followed the instructions above in `setup` -1. Go to the folder that was created using the `make setup-gas-increase network=` step -1. Fill in all TODOs (search for "TODO" in the folder) in the `.env` and `README` files. Tip: you can run `make deps` followed by `make sign-upgrade` to produce a Tenderly simulation which will help fill in several of the TODOs in the README (and also `make sign-rollback`). -1. Check in the task when it's ready to sign and collect signatures from signers -1. Once executed, check in the records files and mark the task `EXECUTED` in the README. - -## Using the combined gas limit, elasticity, and DA footprint gas scalar template - -This template is used to update the gas limit, elasticity, and DA footprint gas scalar, or roll back the changes (if needed). - -1. Ensure you have followed the instructions above in `setup`, including running `make setup-gas-and-elasticity-increase network=` and then go to the folder that was created by this command. -1. Specify the commit of [Base contracts code](https://github.com/base/contracts) in the `.env` file. -1. Run `make deps`. -1. Fill in any task-specific variables in the `.env` file that have per-network comments (e.g., `OWNER_SAFE`, `SENDER`), using the value for your target network. -1. Ensure the `SENDER` variable in the `.env` file is set to a signer of `OWNER_SAFE`. -1. Set the `FROM_*` and `TO_*` values for gas limit and elasticity in the `.env` file. -1. Calculate the DA footprint gas scalar using the DA limits runbook at `go/base-da-config`. `make da-scalar TARGET_BLOB_COUNT=` is the source of truth for the standard soft-cap policy and calculates `gas_limit / (elasticity * da_soft_cap_blob_count * 32,000)`. Since BPO2, Base has used a DA soft-cap blob count of 21, passed as `TARGET_BLOB_COUNT=21`, to allow the chain to use all L1 DA before raising the L2 base fee. The command prints the `.env` value and the DA table to copy into the task README. Pass `BUILDER_HARD_CAP=` after checking the target network's `op_batcher_throttle_block_size_upper_limit` Config Service value. The task README includes links for the standard network scopes. Set the `FROM_DA_FOOTPRINT_GAS_SCALAR` and `TO_DA_FOOTPRINT_GAS_SCALAR` values in the `.env` file. -1. Build the contracts with `forge build`. -1. Generate the validation file for signers with `make gen-validation`. -1. Generate the rollback validation file for signers with `make gen-validation-rollback`. -1. Double check the `cmd` field at the top of both of the generated validation files and ensure that the value passed to the `--sender` flag matches the `SENDER` env var already defined in the `.env` file. -1. Ensure that all of the fields marked as `TODO` in the tasks's `README.md` have been properly filled out. -1. Check in the task when it's ready to sign and request the facilitators to collect signatures from signers. -1. Once executed, check in the records files and mark the task `EXECUTED` in the README. - -## Using the safe management template - -This template is used to perform ownership management on a Gnosis Safe, like the incident multisig, specifically it can be used to change the owners of the multisig. - -1. Ensure you have followed the instructions above in `setup`, including running `make setup-safe-management network=` and go to the folder that was created by this command. -1. Specify the commit of [Base contracts code](https://github.com/base/contracts) you intend to use in the `.env` file. -1. Enter the directory that was generated for the task (in the first step) and then run `make deps`. -1. Specify the `OWNER_SAFE`, which is the safe multisig where an owner will be replaced and the `SENDER` which should be the address of a current signer of the multisig. -1. Fill in the `OwnerDiff.json` inside the task's directory with the addresses to add to, and remove from, the multisig in their respective fields. -1. Ensure that the `EXISTING_OWNERS_LENGTH` constant value inside the `script/UpdateSigners.s.sol` script is set appropriately, in particular that it equals the exact number of current members of the Incident Multisig Safe (prior to running the task). -1. Build the contracts with `forge build`. -1. Generate the validation file for signers with `make gen-validation`. -1. Double check the `cmd` field at the top of the generated validation file at `validations/base-signer.json` and ensure that the value passed to the `--sender` flag matches the `SENDER` env var already defined in the `.env` file. -1. Check in the task when it's ready to sign and request the facilitators to collect signatures from signers. -1. Once executed, check in the records files and mark the task `EXECUTED` in the README. - -## Using the funding template - -This template is used to fund addresses from a Gnosis Safe. - -1. Ensure you have followed the instructions above in `setup`. -1. Run `make setup-funding network=` and go to the folder that was created by this command. -1. Specify the commit of [Base contracts code](https://github.com/base/contracts) you intend to use in the `.env` file. -1. Run `make deps`. -1. Specify the `SAFE`, which is the safe that will fund the addresses in the `.env` file. -1. Specify the `recipients` and `funds` arrays (in 1e18 units) in the `funding.json` file. -1. Build the contracts with `forge build`. -1. Simulate the task with `make sign` and update the generic validations in `VALIDATION.md` with the real values. -1. Check in the task when it's ready to sign and request the facilitators to collect signatures from signers. -1. Once executed, check in the records files and mark the task `EXECUTED` in the README. - -## Using the Base Bridge set partner threshold template - -This template is used to update the partner threshold in [Base Bridge](https://github.com/base/bridge), affecting the amount of required partner signatures to approve bridge messages. - -1. Ensure you have followed the instructions above in `setup`. -1. Run `make setup-bridge-partner-threshold network=` and go to the folder that was created by this command. -1. Specify the commit of [Base contracts code](https://github.com/base/contracts) you intend to use in the `.env` file. -1. Run `make deps`. -1. Fill in any task-specific variables in the `.env` file that have per-network comments, using the value for your target network. -1. Set the `NEW_THRESHOLD` variable in the `.env` file. -1. Ensure the `--sender` flag in the `make gen-validation` command in the `Makefile` file is set to a signer for `OWNER_SAFE` in `.env`. -1. Build the contracts with `forge build`. -1. Generate the validation file for signers with `make gen-validation`. -1. Check in the task when it's ready to sign and request the facilitators to collect signatures from signers. -1. Once executed, check in the records files and mark the task `EXECUTED` in the README. - -## Using the pause Base Bridge template - -This template is used to pause or un-pause [Base Bridge](https://github.com/base/bridge). - -1. Ensure you have followed the instructions above in `setup`. -1. Run `make setup-bridge-pause network=` and go to the folder that was created by this command. -1. Specify the commit of [Base contracts code](https://github.com/base/contracts) you intend to use in the `.env` file. -1. Run `make deps`. -1. Fill in any task-specific variables in the `.env` file that have per-network comments (e.g., `L2_BRIDGE`), using the value for your target network. -1. Set the `IS_PAUSED` variable to `true` or `false` in the `.env` file depending on if you intend to pause or unpause the bridge. -1. Ensure the `SENDER` variable in the Makefile is set to a signer for `OWNER_SAFE`. -1. Build the contracts with `forge build`. -1. Generate the validation file for signers with `make gen-validation`. -1. Check in the task when it's ready to sign and request the facilitators to collect signatures from signers. -1. Once executed, check in the records files and mark the task `EXECUTED` in the README. - -## Using the pause SuperchainConfig template - -This template is used to pause or un-pause the L1 SuperchainConfig contract. - -1. Ensure you have followed the instructions above in `setup`. -1. Run `make setup-superchain-config-pause network=` and go to the folder that was created by this command. -1. Specify the commit of [Base contracts code](https://github.com/base/contracts) you intend to use in the `.env` file. -1. Run `make deps`. -1. Fill in any task-specific variables in the `.env` file that have per-network comments, using the value for your target network. -1. Build the contracts with `forge build`. -1. Sign the pause transaction with `make sign-pause` or generate the validation file for un-pausing with `make gen-validation-unpause`. -1. Check in the task when it's ready to sign and request the facilitators to collect signatures from signers. -1. Once executed, check in the records files and mark the task `EXECUTED` in the README. +For active EVM tasks, the signed payload is `config/` and signatures +are written outside it to `signatures/`. A validation file may set +`skipTaskOriginValidation: true` when task-origin validation is not required. diff --git a/active/evm/Makefile b/active/evm/Makefile new file mode 100644 index 00000000..39f9290b --- /dev/null +++ b/active/evm/Makefile @@ -0,0 +1,79 @@ +EVM_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) + +include $(EVM_ROOT)/../../Makefile +include $(EVM_ROOT)/../../Multisig.mk + +.DEFAULT_GOAL := help + +.PHONY: help +help: + @echo "Usage: make -C active/evm TASK_ID= TASK_NETWORK= " + +# Help does not need a task. Every operational target does. +REQUESTED_GOALS := $(if $(MAKECMDGOALS),$(MAKECMDGOALS),help) +ifneq ($(strip $(filter-out help,$(REQUESTED_GOALS))),) +ifeq ($(strip $(TASK_ID)),) +$(error TASK_ID is required) +endif + +TASKS_DIR := $(realpath $(EVM_ROOT)/tasks) +TASK_DIR := $(realpath $(TASKS_DIR)/$(TASK_ID)) +ifeq ($(TASK_DIR),) +$(error Task does not exist: tasks/$(TASK_ID)) +endif +ifneq ($(abspath $(dir $(TASK_DIR))),$(TASKS_DIR)) +$(error TASK_ID must name one direct child of active/evm/tasks) +endif + +TASK_MK := $(TASK_DIR)/Task.mk +ifeq ($(wildcard $(TASK_MK)),) +$(error Missing $(TASK_MK)) +endif +include $(TASK_MK) + +ifeq ($(strip $(TASK_NETWORK)),) +$(error TASK_NETWORK is required, either on the command line or in Task.mk) +endif + +CONFIG_ROOT := $(realpath $(TASK_DIR)/config) +CONFIG_DIR := $(realpath $(CONFIG_ROOT)/$(TASK_NETWORK)) +ifeq ($(CONFIG_DIR),) +$(error Task network does not exist: tasks/$(TASK_ID)/config/$(TASK_NETWORK)) +endif +ifneq ($(abspath $(dir $(CONFIG_DIR))),$(CONFIG_ROOT)) +$(error TASK_NETWORK must name one direct child of the task config directory) +endif + +TASK_DIR_REL := tasks/$(TASK_ID) +CONFIG_DIR_REL := $(TASK_DIR_REL)/config/$(TASK_NETWORK) +export FOUNDRY_BROADCAST := $(TASK_DIR_REL) + +include $(CONFIG_DIR)/network.env +include $(CONFIG_DIR)/.env + +SUPPORTED_OPERATIONS := funding gas safe-management bridge-threshold pause-bridge superchain-pause custom +ifneq ($(words $(OPERATION)),1) +$(error OPERATION must contain exactly one operation name) +endif +ifeq ($(filter $(SUPPORTED_OPERATIONS),$(OPERATION)),) +$(error Unsupported OPERATION "$(OPERATION)"; expected one of: $(SUPPORTED_OPERATIONS)) +endif + +PROJECT_DIR := $(EVM_ROOT) +SIGNER_TOOL_PATH := $(REPO_ROOT)/signer-tool +VALIDATIONS_DIR := $(EVM_ROOT)/$(CONFIG_DIR_REL)/validations +TASK_ORIGIN_DIR := $(EVM_ROOT)/$(CONFIG_DIR_REL) +SIGNATURE_DIR := $(EVM_ROOT)/$(TASK_DIR_REL)/signatures/$(TASK_NETWORK) +RPC_URL ?= $(L1_RPC_URL) + +ifneq ($(OPERATION),custom) +include $(EVM_ROOT)/make/$(OPERATION).mk +endif + +.PHONY: show-config +show-config: + @echo "OPERATION=$(OPERATION)" + @echo "SCRIPT_NAME=$(SCRIPT_NAME)" + @echo "CONFIG_DIR=$(CONFIG_DIR_REL)" + @echo "FOUNDRY_BROADCAST=$(FOUNDRY_BROADCAST)" +endif diff --git a/active/evm/make/bridge-threshold.mk b/active/evm/make/bridge-threshold.mk new file mode 100644 index 00000000..14546238 --- /dev/null +++ b/active/evm/make/bridge-threshold.mk @@ -0,0 +1,22 @@ +SCRIPT_NAME ?= script/common/bridge/SetThreshold.s.sol:SetThreshold +OWNER_SAFE ?= $(INCIDENT_MULTISIG) +L1_PORTAL ?= $(OPTIMISM_PORTAL) +BRIDGE_THRESHOLD_ENV = OWNER_SAFE=$(OWNER_SAFE) L1_PORTAL=$(L1_PORTAL) L2_BRIDGE_VALIDATOR=$(L2_BRIDGE_VALIDATOR) NEW_THRESHOLD=$(NEW_THRESHOLD) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) + +.PHONY: validate-config +validate-config: + @test -n "$(BASE_CONTRACTS_COMMIT)" -a "$(BASE_CONTRACTS_COMMIT)" != "TODO" || (echo "BASE_CONTRACTS_COMMIT required" && exit 1) + @test -n "$(OWNER_SAFE)" || (echo "OWNER_SAFE required" && exit 1) + @test -n "$(L1_PORTAL)" || (echo "L1_PORTAL required" && exit 1) + @test -n "$(L2_BRIDGE_VALIDATOR)" || (echo "L2_BRIDGE_VALIDATOR required" && exit 1) + @test -n "$(NEW_THRESHOLD)" || (echo "NEW_THRESHOLD required" && exit 1) + @test -n "$(SENDER)" -a "$(SENDER)" != "TODO" || (echo "SENDER required" && exit 1) + @test "$(RECORD_STATE_DIFF)" = "true" || (echo "RECORD_STATE_DIFF=true required" && exit 1) + +.PHONY: gen-validation +gen-validation: validate-config deps-signer-tool + $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer.json,$(BRIDGE_THRESHOLD_ENV)) + +.PHONY: execute +execute: validate-config + $(BRIDGE_THRESHOLD_ENV) $(call MULTISIG_EXECUTE,$(SIGNATURES)) diff --git a/active/evm/make/check-dispatch.sh b/active/evm/make/check-dispatch.sh new file mode 100755 index 00000000..cc5d8ddf --- /dev/null +++ b/active/evm/make/check-dispatch.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +evm_root=$(cd "$(dirname "$0")/.." && pwd) +task_id=".make-check-$$" +task_dir="$evm_root/tasks/$task_id" +trap 'rm -rf "$task_dir"' EXIT + +mkdir -p "$task_dir/config/testnet" +cat > "$task_dir/config/testnet/network.env" <<'EOF' +L1_RPC_URL=http://127.0.0.1:8545 +L2_RPC_URL=http://127.0.0.1:9545 +LEDGER_ACCOUNT=0 +EOF +cat > "$task_dir/config/testnet/.env" <<'EOF' +BASE_CONTRACTS_COMMIT=test +RECORD_STATE_DIFF=true +EOF + +check() { + local operation=$1 mode=${2:-} expected=$3 + { + echo "OPERATION := $operation" + echo "TASK_NETWORK ?= testnet" + [[ -z "$mode" ]] || echo "GAS_MODE := $mode" + } > "$task_dir/Task.mk" + + output=$(make -s -C "$evm_root" TASK_ID="$task_id" show-config) + grep -Fq "SCRIPT_NAME=$expected" <<< "$output" +} + +check funding "" script/common/funding/Fund.s.sol:FundScript +check gas gas-only script/common/gas/SetGasLimit.s.sol:SetGasLimit +check gas combined script/common/gas/IncreaseEip1559ElasticityAndIncreaseGasLimit.s.sol:IncreaseEip1559ElasticityAndIncreaseGasLimitScript +check safe-management "" script/common/safe/UpdateSigners.s.sol:UpdateSigners +check bridge-threshold "" script/common/bridge/SetThreshold.s.sol:SetThreshold +check pause-bridge "" script/common/bridge/PauseBridge.s.sol:PauseBridge +check superchain-pause "" script/common/superchain/PauseSuperchainConfig.s.sol:PauseSuperchainConfig + +cat > "$task_dir/Task.mk" <<'EOF' +OPERATION := custom +TASK_NETWORK ?= testnet +SCRIPT_NAME := script/common/ownership/TransferSystemConfigOwnership.s.sol:TransferSystemConfigOwnership +EOF +output=$(make -s -C "$evm_root" TASK_ID="$task_id" show-config) +grep -Fq "SCRIPT_NAME=script/common/ownership/TransferSystemConfigOwnership.s.sol:TransferSystemConfigOwnership" <<< "$output" +grep -Fq "FOUNDRY_BROADCAST=tasks/$task_id" <<< "$output" + +echo "active/evm Make dispatcher checks passed" diff --git a/active/evm/make/funding.mk b/active/evm/make/funding.mk new file mode 100644 index 00000000..e92c7de8 --- /dev/null +++ b/active/evm/make/funding.mk @@ -0,0 +1,19 @@ +SCRIPT_NAME ?= script/common/funding/Fund.s.sol:FundScript +FUNDING_JSON ?= $(CONFIG_DIR_REL)/funding.json +FUNDING_ENV = OWNER_SAFE=$(OWNER_SAFE) FUNDING_JSON=$(FUNDING_JSON) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) + +.PHONY: validate-config +validate-config: + @test -n "$(BASE_CONTRACTS_COMMIT)" -a "$(BASE_CONTRACTS_COMMIT)" != "TODO" || (echo "BASE_CONTRACTS_COMMIT required" && exit 1) + @test -n "$(OWNER_SAFE)" -a "$(OWNER_SAFE)" != "TODO" || (echo "OWNER_SAFE required" && exit 1) + @test -n "$(SENDER)" -a "$(SENDER)" != "TODO" || (echo "SENDER required" && exit 1) + @test "$(RECORD_STATE_DIFF)" = "true" || (echo "RECORD_STATE_DIFF=true required" && exit 1) + @test -f "$(FUNDING_JSON)" || (echo "Missing $(FUNDING_JSON)" && exit 1) + +.PHONY: gen-validation +gen-validation: validate-config deps-signer-tool + $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer.json,$(FUNDING_ENV)) + +.PHONY: execute +execute: validate-config + $(FUNDING_ENV) $(call MULTISIG_EXECUTE,$(SIGNATURES)) diff --git a/active/evm/make/gas.mk b/active/evm/make/gas.mk new file mode 100644 index 00000000..b6e90f3e --- /dev/null +++ b/active/evm/make/gas.mk @@ -0,0 +1,81 @@ +GAS_MODE ?= combined +ROLLBACK_NONCE_OFFSET ?= 1 + +ifeq ($(GAS_MODE),gas-only) +SCRIPT_NAME ?= script/common/gas/SetGasLimit.s.sol:SetGasLimit +GAS_UPGRADE_ENV = OWNER_SAFE=$(OWNER_SAFE) SYSTEM_CONFIG=$(SYSTEM_CONFIG) OLD_GAS_LIMIT=$(OLD_GAS_LIMIT) NEW_GAS_LIMIT=$(NEW_GAS_LIMIT) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) +GAS_ROLLBACK_ENV = OWNER_SAFE=$(OWNER_SAFE) SYSTEM_CONFIG=$(SYSTEM_CONFIG) OLD_GAS_LIMIT=$(NEW_GAS_LIMIT) NEW_GAS_LIMIT=$(OLD_GAS_LIMIT) SAFE_NONCE=$(ROLLBACK_SAFE_NONCE) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) +else ifeq ($(GAS_MODE),combined) +SCRIPT_NAME ?= script/common/gas/IncreaseEip1559ElasticityAndIncreaseGasLimit.s.sol:IncreaseEip1559ElasticityAndIncreaseGasLimitScript +GAS_UPGRADE_ENV = OWNER_SAFE=$(OWNER_SAFE) SYSTEM_CONFIG=$(SYSTEM_CONFIG) OLD_GAS_LIMIT=$(OLD_GAS_LIMIT) NEW_GAS_LIMIT=$(NEW_GAS_LIMIT) OLD_ELASTICITY=$(OLD_ELASTICITY) NEW_ELASTICITY=$(NEW_ELASTICITY) OLD_DA_FOOTPRINT_GAS_SCALAR=$(OLD_DA_FOOTPRINT_GAS_SCALAR) NEW_DA_FOOTPRINT_GAS_SCALAR=$(NEW_DA_FOOTPRINT_GAS_SCALAR) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) +GAS_ROLLBACK_ENV = OWNER_SAFE=$(OWNER_SAFE) SYSTEM_CONFIG=$(SYSTEM_CONFIG) OLD_GAS_LIMIT=$(NEW_GAS_LIMIT) NEW_GAS_LIMIT=$(OLD_GAS_LIMIT) OLD_ELASTICITY=$(NEW_ELASTICITY) NEW_ELASTICITY=$(OLD_ELASTICITY) OLD_DA_FOOTPRINT_GAS_SCALAR=$(NEW_DA_FOOTPRINT_GAS_SCALAR) NEW_DA_FOOTPRINT_GAS_SCALAR=$(OLD_DA_FOOTPRINT_GAS_SCALAR) SAFE_NONCE=$(ROLLBACK_SAFE_NONCE) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) +else +$(error GAS_MODE must be gas-only or combined) +endif + +ROLLBACK_SAFE_NONCE ?= $(shell expr $(call GET_NONCE,$(OWNER_SAFE)) + $(ROLLBACK_NONCE_OFFSET)) + +.PHONY: validate-config +validate-config: + @test -n "$(BASE_CONTRACTS_COMMIT)" -a "$(BASE_CONTRACTS_COMMIT)" != "TODO" || (echo "BASE_CONTRACTS_COMMIT required" && exit 1) + @test -n "$(OWNER_SAFE)" -a "$(OWNER_SAFE)" != "TODO" || (echo "OWNER_SAFE required" && exit 1) + @test -n "$(SYSTEM_CONFIG)" -a "$(SYSTEM_CONFIG)" != "TODO" || (echo "SYSTEM_CONFIG required" && exit 1) + @test -n "$(SENDER)" -a "$(SENDER)" != "TODO" || (echo "SENDER required" && exit 1) + @test -n "$(OLD_GAS_LIMIT)" -a "$(OLD_GAS_LIMIT)" != "TODO" || (echo "OLD_GAS_LIMIT required" && exit 1) + @test -n "$(NEW_GAS_LIMIT)" -a "$(NEW_GAS_LIMIT)" != "TODO" || (echo "NEW_GAS_LIMIT required" && exit 1) + @test "$(RECORD_STATE_DIFF)" = "true" || (echo "RECORD_STATE_DIFF=true required" && exit 1) +ifeq ($(GAS_MODE),combined) + @test -n "$(OLD_ELASTICITY)" -a "$(OLD_ELASTICITY)" != "TODO" || (echo "OLD_ELASTICITY required" && exit 1) + @test -n "$(NEW_ELASTICITY)" -a "$(NEW_ELASTICITY)" != "TODO" || (echo "NEW_ELASTICITY required" && exit 1) + @test -n "$(OLD_DA_FOOTPRINT_GAS_SCALAR)" -a "$(OLD_DA_FOOTPRINT_GAS_SCALAR)" != "TODO" || (echo "OLD_DA_FOOTPRINT_GAS_SCALAR required" && exit 1) + @test -n "$(NEW_DA_FOOTPRINT_GAS_SCALAR)" -a "$(NEW_DA_FOOTPRINT_GAS_SCALAR)" != "TODO" || (echo "NEW_DA_FOOTPRINT_GAS_SCALAR required" && exit 1) +endif + +.PHONY: gen-validation +gen-validation: validate-config deps-signer-tool + $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer.json,$(GAS_UPGRADE_ENV)) + +.PHONY: gen-validation-rollback +gen-validation-rollback: validate-config deps-signer-tool + $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer-rollback.json,$(GAS_ROLLBACK_ENV)) + +.PHONY: sign-upgrade +sign-upgrade: validate-config + $(GAS_UPGRADE_ENV) $(GOPATH)/bin/eip712sign --ledger --hd-paths $(LEDGER_HD_PATH) -- \ + $(MISE_EXEC) forge script --rpc-url $(RPC_URL) $(SCRIPT_NAME) --sig "sign(address[])" "[]" + +.PHONY: sign-rollback +sign-rollback: validate-config + $(GAS_ROLLBACK_ENV) $(GOPATH)/bin/eip712sign --ledger --hd-paths $(LEDGER_HD_PATH) -- \ + $(MISE_EXEC) forge script --rpc-url $(RPC_URL) $(SCRIPT_NAME) --sig "sign(address[])" "[]" + +.PHONY: execute execute-upgrade execute-rollback +execute: validate-config + $(GAS_UPGRADE_ENV) $(call MULTISIG_EXECUTE,$(SIGNATURES)) + +execute-upgrade: execute + +execute-rollback: validate-config + $(GAS_ROLLBACK_ENV) $(call MULTISIG_EXECUTE,$(SIGNATURES)) + +ifeq ($(GAS_MODE),combined) +BUILDER_HARD_CAP ?= TODO + +.PHONY: da-scalar +da-scalar: +ifndef TARGET_BLOB_COUNT + $(error TARGET_BLOB_COUNT is required) +endif + @scalar=$$(( $(NEW_GAS_LIMIT) / ($(NEW_ELASTICITY) * $(TARGET_BLOB_COUNT) * 32000) )); \ + soft_cap=$$(( $(TARGET_BLOB_COUNT) * 32000 )); \ + echo "NEW_DA_FOOTPRINT_GAS_SCALAR=$$scalar"; \ + echo; \ + echo "| Field | Value |"; \ + echo "|-------|-------|"; \ + echo "| Gas limit | \`$(NEW_GAS_LIMIT)\` |"; \ + echo "| Elasticity | \`$(NEW_ELASTICITY)\` |"; \ + echo "| DA soft-cap blob count | \`$(TARGET_BLOB_COUNT)\` |"; \ + echo "| Calculated scalar | \`$$scalar\` |"; \ + echo "| Implied soft cap | \`$$soft_cap\` estimated DA bytes per L2 block |"; \ + echo "| Builder hard cap | \`$(BUILDER_HARD_CAP)\` estimated DA bytes per L2 block |" +endif diff --git a/active/evm/make/pause-bridge.mk b/active/evm/make/pause-bridge.mk new file mode 100644 index 00000000..00b13a20 --- /dev/null +++ b/active/evm/make/pause-bridge.mk @@ -0,0 +1,81 @@ +SCRIPT_NAME ?= script/common/bridge/PauseBridge.s.sol:PauseBridge +OWNER_SAFE ?= $(INCIDENT_MULTISIG) +L1_PORTAL ?= $(OPTIMISM_PORTAL) +SAFE_NONCE ?= $(call GET_NONCE,$(OWNER_SAFE)) + +ZERO_ADDRESS := 0x0000000000000000000000000000000000000000 +SAFE_TX_TYPEHASH := 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8 +SAFE_TX_ABI := f(bytes32,address,uint256,bytes32,uint8,uint256,uint256,uint256,address,address,uint256) +PAUSE_SIGNATURES_FILE ?= $(CONFIG_DIR_REL)/signatures-pause.txt +UNPAUSE_SIGNATURES_FILE ?= $(CONFIG_DIR_REL)/signatures-unpause.txt +PAUSE_BRIDGE_ENV = OWNER_SAFE=$(OWNER_SAFE) L1_PORTAL=$(L1_PORTAL) L2_BRIDGE=$(L2_BRIDGE) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) + +.PHONY: validate-config +validate-config: + @test -n "$(BASE_CONTRACTS_COMMIT)" -a "$(BASE_CONTRACTS_COMMIT)" != "TODO" || (echo "BASE_CONTRACTS_COMMIT required" && exit 1) + @test -n "$(OWNER_SAFE)" || (echo "OWNER_SAFE required" && exit 1) + @test -n "$(L1_PORTAL)" || (echo "L1_PORTAL required" && exit 1) + @test -n "$(L2_BRIDGE)" || (echo "L2_BRIDGE required" && exit 1) + @test -n "$(SENDER)" -a "$(SENDER)" != "TODO" || (echo "SENDER required" && exit 1) + @test "$(RECORD_STATE_DIFF)" = "true" || (echo "RECORD_STATE_DIFF=true required" && exit 1) + +.PHONY: gen-validation +gen-validation: validate-config deps-signer-tool + $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer.json,$(PAUSE_BRIDGE_ENV) IS_PAUSED=true) + +.PHONY: sign-pause +sign-pause: validate-config + @rm -f "$(PAUSE_SIGNATURES_FILE)" + @mkdir -p "$(dir $(PAUSE_SIGNATURES_FILE))" + @set -e; \ + domain_separator=$$($(MISE_EXEC) cast call $(OWNER_SAFE) "domainSeparator()(bytes32)" --rpc-url $(L1_RPC_URL)); \ + bridge_data=$$($(MISE_EXEC) cast calldata "setPaused(bool)" true); \ + portal_data=$$($(MISE_EXEC) cast calldata "depositTransaction(address,uint256,uint64,bool,bytes)" $(L2_BRIDGE) 0 100000 false $$bridge_data); \ + portal_data_hash=$$($(MISE_EXEC) cast keccak "$$portal_data"); \ + for i in $$(seq 0 19); do \ + nonce=$$(($(SAFE_NONCE) + $$i)); \ + echo "Signing pause with nonce $$nonce"; \ + safe_tx=$$($(MISE_EXEC) cast abi-encode '$(SAFE_TX_ABI)' $(SAFE_TX_TYPEHASH) $(L1_PORTAL) 0 $$portal_data_hash 0 0 0 0 $(ZERO_ADDRESS) $(ZERO_ADDRESS) $$nonce); \ + message_hash=$$($(MISE_EXEC) cast keccak "$$safe_tx"); \ + signing_data="0x1901$${domain_separator#0x}$${message_hash#0x}"; \ + $(GOPATH)/bin/eip712sign --ledger --hd-paths $(LEDGER_HD_PATH) -data "$$signing_data" > sign_output.tmp; \ + cat sign_output.tmp; \ + echo "Nonce: $$nonce" >> "$(PAUSE_SIGNATURES_FILE)"; \ + grep -E "^Data:|^Signer:|^Signature:" sign_output.tmp >> "$(PAUSE_SIGNATURES_FILE)" || true; \ + echo >> "$(PAUSE_SIGNATURES_FILE)"; \ + rm -f sign_output.tmp; \ + done + +.PHONY: sign-unpause +sign-unpause: validate-config + @rm -f "$(UNPAUSE_SIGNATURES_FILE)" + @mkdir -p "$(dir $(UNPAUSE_SIGNATURES_FILE))" + @set -e; \ + domain_separator=$$($(MISE_EXEC) cast call $(OWNER_SAFE) "domainSeparator()(bytes32)" --rpc-url $(L1_RPC_URL)); \ + bridge_data=$$($(MISE_EXEC) cast calldata "setPaused(bool)" false); \ + portal_data=$$($(MISE_EXEC) cast calldata "depositTransaction(address,uint256,uint64,bool,bytes)" $(L2_BRIDGE) 0 100000 false $$bridge_data); \ + portal_data_hash=$$($(MISE_EXEC) cast keccak "$$portal_data"); \ + for i in $$(seq 0 19); do \ + nonce=$$(($(SAFE_NONCE) + $$i)); \ + echo "Signing unpause with nonce $$nonce"; \ + safe_tx=$$($(MISE_EXEC) cast abi-encode '$(SAFE_TX_ABI)' $(SAFE_TX_TYPEHASH) $(L1_PORTAL) 0 $$portal_data_hash 0 0 0 0 $(ZERO_ADDRESS) $(ZERO_ADDRESS) $$nonce); \ + message_hash=$$($(MISE_EXEC) cast keccak "$$safe_tx"); \ + signing_data="0x1901$${domain_separator#0x}$${message_hash#0x}"; \ + $(GOPATH)/bin/eip712sign --ledger --hd-paths $(LEDGER_HD_PATH) -data "$$signing_data" > sign_output.tmp; \ + cat sign_output.tmp; \ + echo "Nonce: $$nonce" >> "$(UNPAUSE_SIGNATURES_FILE)"; \ + grep -E "^Data:|^Signer:|^Signature:" sign_output.tmp >> "$(UNPAUSE_SIGNATURES_FILE)" || true; \ + echo >> "$(UNPAUSE_SIGNATURES_FILE)"; \ + rm -f sign_output.tmp; \ + done + +.PHONY: execute-pause execute-unpause +execute-pause: validate-config + $(PAUSE_BRIDGE_ENV) IS_PAUSED=true $(call MULTISIG_EXECUTE,$(SIGNATURES)) + +execute-unpause: validate-config + $(PAUSE_BRIDGE_ENV) IS_PAUSED=false $(call MULTISIG_EXECUTE,$(SIGNATURES)) + +.PHONY: check-status +check-status: + $(MISE_EXEC) cast call $(L2_BRIDGE) "paused()(bool)" --rpc-url $(L2_RPC_URL) diff --git a/active/evm/make/safe-management.mk b/active/evm/make/safe-management.mk new file mode 100644 index 00000000..7a81a7f9 --- /dev/null +++ b/active/evm/make/safe-management.mk @@ -0,0 +1,19 @@ +SCRIPT_NAME ?= script/common/safe/UpdateSigners.s.sol:UpdateSigners +OWNER_DIFF_JSON ?= $(CONFIG_DIR_REL)/OwnerDiff.json +SAFE_MANAGEMENT_ENV = OWNER_SAFE=$(OWNER_SAFE) OWNER_DIFF_JSON=$(OWNER_DIFF_JSON) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) + +.PHONY: validate-config +validate-config: + @test -n "$(BASE_CONTRACTS_COMMIT)" -a "$(BASE_CONTRACTS_COMMIT)" != "TODO" || (echo "BASE_CONTRACTS_COMMIT required" && exit 1) + @test -n "$(OWNER_SAFE)" -a "$(OWNER_SAFE)" != "TODO" || (echo "OWNER_SAFE required" && exit 1) + @test -n "$(SENDER)" -a "$(SENDER)" != "TODO" || (echo "SENDER required" && exit 1) + @test "$(RECORD_STATE_DIFF)" = "true" || (echo "RECORD_STATE_DIFF=true required" && exit 1) + @test -f "$(OWNER_DIFF_JSON)" || (echo "Missing $(OWNER_DIFF_JSON)" && exit 1) + +.PHONY: gen-validation +gen-validation: validate-config deps-signer-tool + $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer.json,$(SAFE_MANAGEMENT_ENV)) + +.PHONY: execute +execute: validate-config + $(SAFE_MANAGEMENT_ENV) $(call MULTISIG_EXECUTE,$(SIGNATURES)) diff --git a/active/evm/make/superchain-pause.mk b/active/evm/make/superchain-pause.mk new file mode 100644 index 00000000..7c541901 --- /dev/null +++ b/active/evm/make/superchain-pause.mk @@ -0,0 +1,61 @@ +SCRIPT_NAME ?= script/common/superchain/PauseSuperchainConfig.s.sol:PauseSuperchainConfig + +ZERO_ADDRESS := 0x0000000000000000000000000000000000000000 +SAFE_TX_TYPEHASH := 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8 +SAFE_TX_ABI := f(bytes32,address,uint256,bytes32,uint8,uint256,uint256,uint256,address,address,uint256) +PAUSE_SIGNATURES_FILE ?= $(CONFIG_DIR_REL)/signatures-pause.txt +SUPERCHAIN_PAUSE_ENV = INCIDENT_MULTISIG=$(INCIDENT_MULTISIG) SYSTEM_CONFIG=$(SYSTEM_CONFIG) RECORD_STATE_DIFF=$(RECORD_STATE_DIFF) + +.PHONY: validate-config +validate-config: + @test -n "$(BASE_CONTRACTS_COMMIT)" -a "$(BASE_CONTRACTS_COMMIT)" != "TODO" || (echo "BASE_CONTRACTS_COMMIT required" && exit 1) + @test -n "$(INCIDENT_MULTISIG)" || (echo "INCIDENT_MULTISIG required" && exit 1) + @test -n "$(SYSTEM_CONFIG)" || (echo "SYSTEM_CONFIG required" && exit 1) + @test -n "$(SENDER)" -a "$(SENDER)" != "TODO" || (echo "SENDER required" && exit 1) + @test "$(RECORD_STATE_DIFF)" = "true" || (echo "RECORD_STATE_DIFF=true required" && exit 1) + +.PHONY: gen-validation +gen-validation: validate-config deps-signer-tool + $(call GEN_VALIDATION,$(SCRIPT_NAME),,$(SENDER),base-signer.json,$(SUPERCHAIN_PAUSE_ENV)) + +.PHONY: sign-pause +sign-pause: validate-config + @rm -f "$(PAUSE_SIGNATURES_FILE)" + @mkdir -p "$(dir $(PAUSE_SIGNATURES_FILE))" + @set -e; \ + start_nonce_hex=$$($(MISE_EXEC) cast call $(INCIDENT_MULTISIG) "nonce()(uint256)" --rpc-url $(L1_RPC_URL)); \ + start_nonce=$$($(MISE_EXEC) cast to-dec "$$start_nonce_hex"); \ + superchain_config=$$($(MISE_EXEC) cast call $(SYSTEM_CONFIG) "superchainConfig()(address)" --rpc-url $(L1_RPC_URL)); \ + domain_separator=$$($(MISE_EXEC) cast call $(INCIDENT_MULTISIG) "domainSeparator()(bytes32)" --rpc-url $(L1_RPC_URL)); \ + call_data=$$($(MISE_EXEC) cast calldata "pause(address)" $(ZERO_ADDRESS)); \ + call_data_hash=$$($(MISE_EXEC) cast keccak "$$call_data"); \ + echo "Starting nonce: $$start_nonce"; \ + for i in $$(seq 0 19); do \ + nonce=$$(($$start_nonce + $$i)); \ + echo "Signing with nonce $$nonce"; \ + safe_tx=$$($(MISE_EXEC) cast abi-encode '$(SAFE_TX_ABI)' $(SAFE_TX_TYPEHASH) $$superchain_config 0 $$call_data_hash 0 0 0 0 $(ZERO_ADDRESS) $(ZERO_ADDRESS) $$nonce); \ + message_hash=$$($(MISE_EXEC) cast keccak "$$safe_tx"); \ + signing_data="0x1901$${domain_separator#0x}$${message_hash#0x}"; \ + $(GOPATH)/bin/eip712sign --ledger --hd-paths $(LEDGER_HD_PATH) -data "$$signing_data" > sign_output.tmp; \ + cat sign_output.tmp; \ + signer=$$(grep "^Signer:" sign_output.tmp | awk '{print $$2}'); \ + sig=$$(grep "^Signature:" sign_output.tmp | awk '{print $$2}'); \ + printf "%s," "$$signer:$$nonce:$$sig" >> "$(PAUSE_SIGNATURES_FILE)"; \ + rm -f sign_output.tmp; \ + done; \ + echo >> "$(PAUSE_SIGNATURES_FILE)" + +.PHONY: execute-pause +execute-pause: validate-config + $(SUPERCHAIN_PAUSE_ENV) $(call MULTISIG_EXECUTE,$(SIGNATURES)) + +.PHONY: check-status +check-status: + @superchain_config=$$($(MISE_EXEC) cast call $(SYSTEM_CONFIG) "superchainConfig()(address)" --rpc-url $(L1_RPC_URL)); \ + echo "SuperchainConfig address: $$superchain_config"; \ + $(MISE_EXEC) cast call $$superchain_config "paused(address)(bool)" $(ZERO_ADDRESS) --rpc-url $(L1_RPC_URL) + +.PHONY: check-nonce +check-nonce: + @echo "Incident Safe: $(INCIDENT_MULTISIG)" + @$(MISE_EXEC) cast call $(INCIDENT_MULTISIG) "nonce()(uint256)" --rpc-url $(L1_RPC_URL) diff --git a/active/evm/script/common/README.md b/active/evm/script/common/README.md index df185461..9a918079 100644 --- a/active/evm/script/common/README.md +++ b/active/evm/script/common/README.md @@ -1,6 +1,8 @@ # Common EVM Scripts -Reusable EVM operation scripts live here. A script belongs in this directory when it is expected to be reused across tasks, networks, or task templates. Keep one-off task glue outside this directory. +Reusable EVM operation scripts live here. A script belongs in this directory +when it is expected to be reused across tasks or networks. Keep one-off task +glue outside this directory. ## How To Use @@ -24,16 +26,20 @@ This keeps validation JSON portable and avoids ambiguity when different files de | --- | --- | --- | --- | | `verifier-update/` | `DeployAggregateVerifier.s.sol` | Deploys a replacement `AggregateVerifier` by copying immutable constructor inputs from the live implementation and replacing verifier hashes. | `tasks//config//.env`, `tasks//config//network.env`, `ADDRESSES_JSON` output path | | `verifier-update/` | `UpdateVerifierHashes.s.sol` | Multisig script that updates `DisputeGameFactory.gameImpls(gameType)` to a deployed `AggregateVerifier`. | `ADDRESSES_JSON` pointing at a JSON file with `aggregateVerifier` | -| `funding/` | `Fund.s.sol` | Sends native token from a Safe to recipients listed in `funding.json`. | `funding.json` | +| `funding/` | `Fund.s.sol` | Sends native token from a Safe to recipients listed in `funding.json`. | `FUNDING_JSON` pointing at task-local JSON | +| `gas/` | `SetGasLimit.s.sol` | Updates only the gas limit on `SystemConfig`. | config env values | | `gas/` | `IncreaseEip1559ElasticityAndIncreaseGasLimit.s.sol` | Updates gas limit, EIP-1559 elasticity, and DA footprint gas scalar on `SystemConfig`. | config env values | | `bridge/` | `PauseBridge.s.sol` | Deposits an L2 transaction through the portal to pause or unpause the L2 bridge. | config env values | | `bridge/` | `SetThreshold.s.sol` | Deposits an L2 transaction through the portal to update bridge partner threshold. | config env values | | `superchain/` | `PauseSuperchainConfig.s.sol` | Pauses the chain through `SuperchainConfig`. | config env values | -| `safe/` | `UpdateSigners.s.sol` | Adds and removes Safe owners while preserving threshold. | `OwnerDiff.json` | +| `safe/` | `UpdateSigners.s.sol` | Adds and removes Safe owners while preserving threshold. | `OWNER_DIFF_JSON` pointing at task-local JSON | | `ownership/` | `TransferSystemConfigOwnership.s.sol` | Multisig script that transfers `SystemConfig` ownership from the current owner to a new owner. | `PROXY_ADMIN_OWNER`, `INCIDENT_MULTISIG`, `SYSTEM_CONFIG` env values | ## Adding Scripts Use exact Solidity pragmas based on the contracts the script imports; preserve a script's original exact pragma when moving it here. Load config from environment variables into immutable variables where possible. Keep task-specific values in `tasks//config//.env`, `tasks//config//network.env`, or task-local JSON files rather than hardcoding them in common scripts. -If a script needs task-specific behavior, prefer a small task-local wrapper or Makefile target that passes environment variables into a common script. Move the underlying Solidity into this directory only when the operation itself is reusable. +Reusable signing and execution workflows live in `active/evm/make/`. `Task.mk` +sets `OPERATION`, and the dispatcher loads the matching workflow. Truly +task-specific targets may remain in `Task.mk`. Move underlying Solidity here +only when the operation itself is reusable. diff --git a/active/evm/script/common/funding/Fund.s.sol b/active/evm/script/common/funding/Fund.s.sol index 4cf991d6..aafff3f6 100644 --- a/active/evm/script/common/funding/Fund.s.sol +++ b/active/evm/script/common/funding/Fund.s.sol @@ -19,7 +19,7 @@ contract FundScript is MultisigScript { constructor() { OWNER_SAFE = vm.envAddress("OWNER_SAFE"); - string memory funding = vm.readFile("./funding.json"); + string memory funding = vm.readFile(vm.envString("FUNDING_JSON")); RECIPIENTS = vm.parseJsonAddressArray(funding, ".recipients"); FUNDS = vm.parseJsonUintArray(funding, ".funds"); diff --git a/active/evm/script/common/gas/SetGasLimit.s.sol b/active/evm/script/common/gas/SetGasLimit.s.sol new file mode 100644 index 00000000..7b6bbf4d --- /dev/null +++ b/active/evm/script/common/gas/SetGasLimit.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {Vm} from "forge-std/Vm.sol"; + +import {Simulation} from "@base-contracts/scripts/universal/Simulation.sol"; +import {MultisigScript, Enum} from "@base-contracts/scripts/universal/MultisigScript.sol"; + +interface ISystemConfigGasLimit { + function gasLimit() external view returns (uint64); + function setGasLimit(uint64 gasLimit) external; +} + +/// @notice Updates only the SystemConfig gas limit. +contract SetGasLimit is MultisigScript { + bytes32 internal constant GAS_LIMIT_SLOT = bytes32(uint256(0x68)); + + address internal immutable OWNER_SAFE = vm.envAddress("OWNER_SAFE"); + address internal immutable SYSTEM_CONFIG = vm.envAddress("SYSTEM_CONFIG"); + uint64 internal immutable OLD_GAS_LIMIT = uint64(vm.envUint("OLD_GAS_LIMIT")); + uint64 internal immutable NEW_GAS_LIMIT = uint64(vm.envUint("NEW_GAS_LIMIT")); + + function _postCheck(Vm.AccountAccess[] memory, Simulation.Payload memory) internal view override { + require( + ISystemConfigGasLimit(SYSTEM_CONFIG).gasLimit() == NEW_GAS_LIMIT, "SetGasLimit: gas limit was not updated" + ); + } + + function _buildCalls() internal view override returns (Call[] memory) { + Call[] memory calls = new Call[](1); + calls[0] = Call({ + operation: Enum.Operation.Call, + target: SYSTEM_CONFIG, + data: abi.encodeCall(ISystemConfigGasLimit.setGasLimit, (NEW_GAS_LIMIT)), + value: 0 + }); + return calls; + } + + function _ownerSafe() internal view override returns (address) { + return OWNER_SAFE; + } + + function _simulationOverrides() internal view override returns (Simulation.StateOverride[] memory) { + Simulation.StateOverride[] memory stateOverrides = new Simulation.StateOverride[](1); + Simulation.StorageOverride[] memory storageOverrides = new Simulation.StorageOverride[](1); + storageOverrides[0] = Simulation.StorageOverride({key: GAS_LIMIT_SLOT, value: bytes32(uint256(OLD_GAS_LIMIT))}); + stateOverrides[0] = Simulation.StateOverride({contractAddress: SYSTEM_CONFIG, overrides: storageOverrides}); + return stateOverrides; + } +} diff --git a/active/evm/script/common/safe/UpdateSigners.s.sol b/active/evm/script/common/safe/UpdateSigners.s.sol index 9efc94d6..13a1246f 100644 --- a/active/evm/script/common/safe/UpdateSigners.s.sol +++ b/active/evm/script/common/safe/UpdateSigners.s.sol @@ -41,9 +41,7 @@ contract UpdateSigners is MultisigScript { THRESHOLD = ownerSafe.getThreshold(); EXISTING_OWNERS = ownerSafe.getOwners(); - string memory rootPath = vm.projectRoot(); - string memory path = string.concat(rootPath, "/OwnerDiff.json"); - string memory jsonData = vm.readFile(path); + string memory jsonData = vm.readFile(vm.envString("OWNER_DIFF_JSON")); OWNERS_TO_ADD = abi.decode(jsonData.parseRaw(".OwnersToAdd"), (address[])); OWNERS_TO_REMOVE = abi.decode(jsonData.parseRaw(".OwnersToRemove"), (address[])); diff --git a/active/evm/tasks/README.md b/active/evm/tasks/README.md new file mode 100644 index 00000000..ed9cb668 --- /dev/null +++ b/active/evm/tasks/README.md @@ -0,0 +1,58 @@ +# Active EVM Tasks + +Each task supplies only task-specific configuration. Reusable Solidity lives in +`../script/common/`, and reusable signing/execution targets live in `../make/`. + +```text +tasks// +├── Task.mk +├── FACILITATOR.md +└── config// + ├── .env + ├── network.env + ├── README.md + ├── validations/ + └── funding.json or OwnerDiff.json # only when required +``` + +Forge writes execution artifacts directly into the task directory, grouped by +script and chain ID. This keeps everything needed to archive a task under +`tasks//` without another `records/` layer. + +`Task.mk` selects an operation and may set a default network. Standard +operations provide their own `SCRIPT_NAME`; custom tasks define it locally. + +```makefile +OPERATION := gas +GAS_MODE := combined +TASK_NETWORK ?= mainnet +``` + +Gas tasks set `GAS_MODE` to `gas-only` or `combined`. + +| Operation | Required task inputs | Reusable targets | +| --- | --- | --- | +| `funding` | `OWNER_SAFE`, `SENDER`, `funding.json` | `gen-validation`, `execute` | +| `gas` / `gas-only` | `OWNER_SAFE`, `SYSTEM_CONFIG`, `SENDER`, `OLD_GAS_LIMIT`, `NEW_GAS_LIMIT` | `gen-validation`, `sign-upgrade`, `execute-upgrade`, `gen-validation-rollback`, `sign-rollback`, `execute-rollback` | +| `gas` / `combined` | Gas-only inputs plus old/new elasticity and DA footprint scalar | Gas-only targets plus `da-scalar` | +| `safe-management` | `OWNER_SAFE`, `SENDER`, `OwnerDiff.json` | `gen-validation`, `execute` | +| `bridge-threshold` | Safe, portal, validator, sender, and new threshold | `gen-validation`, `execute` | +| `pause-bridge` | Safe, portal, L2 bridge, and sender | `gen-validation`, `sign-pause`, `sign-unpause`, `execute-pause`, `execute-unpause`, `check-status` | +| `superchain-pause` | Incident multisig, SystemConfig, and sender | `gen-validation`, `sign-pause`, `execute-pause`, `check-status`, `check-nonce` | +| `custom` | Defined by the task | Defined in `Task.mk` | + +Create the files above, then run the task from the repository root: + +```bash +make -C active/evm TASK_ID= TASK_NETWORK= deps +make -C active/evm TASK_ID= TASK_NETWORK= gen-validation +``` + +Task inputs belong in `config//.env`, including +`BASE_CONTRACTS_COMMIT` and `RECORD_STATE_DIFF=true`. Start the signer README at +`READY TO SIGN`, use simple validation names, and include task-specific +generation, approval, and execution steps in `FACILITATOR.md`. + +Keep RPC URLs, chain IDs, Ledger account, Safe addresses, and shared contract +addresses in `network.env`; keep operation-specific values and JSON paths in +`.env`.