diff --git a/.env.example b/.env.example index 90c30bcf..5ef9d51a 100644 --- a/.env.example +++ b/.env.example @@ -1,52 +1,39 @@ -# NETWORK AND ACCOUNT(s) +# SECRETS (supply locally, never commit — also configurable via `vars`) # --------------------------------------------------- -DEPLOYMENT_PRIVATE_KEY="0x0000000000000000000000000000000000000000000000000000000000000001" -REFUND_ADDRESS="" +DEPLOYER_KEY="0x0000000000000000000000000000000000000000000000000000000000000001" +ETHERSCAN_API_KEY="" -# Used by Foundry -RPC_URL="https://sepolia.infura.io/v3/..." -CHAIN_ID="11155111" - -# Used for log file names -NETWORK_NAME="sepolia" +# Pinata JWT, required by `just pin-metadata` / `just upgrade-proposal` +PINATA_JWT="" -# SOURCE VERIFICATION +# OPTIONAL # --------------------------------------------------- -VERIFIER="etherscan" -# VERIFIER="blockscout" -# VERIFIER="sourcify" -# VERIFIER="routescan-mainnet" # (Etherscan compatible) -# VERIFIER="routescan-testnet" # (Etherscan compatible) -# ETHERSCAN_API_KEY="..." -# BLOCKSCOUT_HOST_NAME="sepolia.explorer.mode.network" +# Burner wallet: destination for `just refund` +# REFUND_ADDRESS="0x..." -# Deployed dependencies -# --------------------------------------------------- -# Used to deploy the contracts and to run fork tests. -# -# Pick the right addresses from: -# https://github.com/aragon/osx/blob/main/packages/artifacts/src/addresses.json -# https://github.com/aragon/token-voting-plugin/blob/main/npm-artifacts/src/addresses.json +# Pin fork tests to a specific block +# FORK_BLOCK_NUMBER= -DAO_FACTORY_ADDRESS="" -PLUGIN_REPO_FACTORY_ADDRESS="" -PLUGIN_SETUP_PROCESSOR_ADDRESS="" - -# DEPLOYMENT PARAMETERS +# PLUGIN SETTINGS (read by the deploy scripts under script/) # --------------------------------------------------- -# New deployment -PLUGIN_ENS_SUBDOMAIN="" # Optional (a random one is used if empty) +# New deployment (DeployNewTokenVotingRepo) PLUGIN_REPO_MAINTAINER_ADDRESS="" +PLUGIN_ENS_SUBDOMAIN="" # Optional (a random one is used if empty) -# New version on a existing repo -PLUGIN_REPO_ADDRESS="" -MANAGEMENT_DAO_MULTISIG_ADDRESS="" - -# Metadata - +# New version on an existing repo (DeployTokenVoting_1_4 / upgrade-proposal). +# TOKEN_VOTING_PLUGIN_REPO_ADDRESS and MANAGEMENT_DAO_MULTISIG_ADDRESS come from the active network file +# (lib/just-foundry/networks/.env); override per-network with `just switch override` if needed. RELEASE_METADATA_URI="" BUILD_METADATA_URI="" -PINATA_JWT="" +# NETWORK CONFIG +# --------------------------------------------------- +# RPC_URL, CHAIN_ID, NETWORK_NAME, VERIFIER and the Aragon OSx addresses +# (DAO_FACTORY_ADDRESS, PLUGIN_REPO_FACTORY_ADDRESS, PLUGIN_SETUP_PROCESSOR_ADDRESS, +# MANAGEMENT_DAO_MULTISIG_ADDRESS, …) come from the active network in +# lib/just-foundry/networks/.env — select it with `just init ` +# or `just switch `, and inspect it with `just env`. +# To override a value for one network: `just switch override`, +# then edit the generated .env. file at the repo root. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd66af90..a7111aa0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,5 @@ jobs: - name: Run Forge tests run: | - cp .env.example .env - make test + forge test -vvv --no-match-path "./test/fork-tests/*.sol" id: test diff --git a/.gitignore b/.gitignore index a9d856be..781ed4c3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,16 +3,17 @@ cache coverage out zkout +logs # Files .env -.env-* +.env.* +!.env.example *.log .DS_Store lcov.info # Testing -test/**/*.tree lcov.info report diff --git a/.gitmodules b/.gitmodules index acf1104f..eba3c582 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "lib/plugin-version-1.3"] path = lib/plugin-version-1.3 url = https://github.com/aragon/token-voting-plugin-hardhat +[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 00000000..77584cbb --- /dev/null +++ b/.vars.yaml @@ -0,0 +1,9 @@ +# vars manifest — secrets this project resolves via `vars` (https://github.com/vars-cli/vars). +# Only key NAMES live here; `vars` fetches the values from your configured backend. +# `vars` is optional: without it, define these in .env instead (see .env.example). +# just-foundry's recipes auto-run `vars resolve` when `vars` is installed and this file exists. +keys: + - DEPLOYER_KEY + - ETHERSCAN_API_KEY + - PINATA_JWT + # - RPC_URL # optional: override the network's default RPC with a private endpoint diff --git a/Makefile b/Makefile deleted file mode 100644 index eacd1ede..00000000 --- a/Makefile +++ /dev/null @@ -1,427 +0,0 @@ -.DEFAULT_TARGET: help - -# Import settings and constants -include .env -include llm.mk - -SHELL:=/bin/bash - -# CONSTANTS - -# NOTE: Choose the appropriate deployment script -DEPLOYMENT_SCRIPT := DeployTokenVoting_1_4 -# DEPLOYMENT_SCRIPT := DeployNewTokenVotingRepo - -SOLC_VERSION := $(shell cat foundry.toml | grep solc | cut -d= -f2 | xargs echo || echo "0.8.28") -SUPPORTED_VERIFIERS := etherscan blockscout sourcify zksync routescan-mainnet routescan-testnet -MAKE_TEST_TREE_CMD := deno run ./script/make-test-tree.ts -VERIFY_CONTRACTS_SCRIPT := script/verify-contracts.sh -TEST_TREE_MARKDOWN := TESTS.md -ARTIFACTS_FOLDER := ./artifacts -LOGS_FOLDER := ./logs -VERBOSITY := -vvv - -# Remove quotes -NETWORK_NAME:=$(strip $(subst ',, $(subst ",,$(NETWORK_NAME)))) -CHAIN_ID:=$(strip $(subst ',, $(subst ",,$(CHAIN_ID)))) -VERIFIER:=$(strip $(subst ',, $(subst ",,$(VERIFIER)))) - -TEST_COVERAGE_SRC_FILES := $(wildcard test/*.sol test/**/*.sol src/*.sol src/**/*.sol) -TEST_SOURCE_FILES := $(wildcard test/*.t.yaml test/fork-tests/*.t.yaml) -TEST_TREE_FILES := $(TEST_SOURCE_FILES:.t.yaml=.tree) -DEPLOYMENT_ADDRESS := $(shell cast wallet address --private-key $(DEPLOYMENT_PRIVATE_KEY) 2>/dev/null || echo "NOTE: DEPLOYMENT_PRIVATE_KEY is not properly set on .env" > /dev/stderr) -DEPLOYMENT_SCRIPT_PARAM := script/$(DEPLOYMENT_SCRIPT).s.sol:$(DEPLOYMENT_SCRIPT)Script - -DEPLOYMENT_LOG_FILE=deployment-$(NETWORK_NAME)-$(shell date +"%y-%m-%d-%H-%M").log - -# Check values - -ifeq ($(filter $(VERIFIER),$(SUPPORTED_VERIFIERS)),) - $(error Unknown verifier: $(VERIFIER). It must be one of: $(SUPPORTED_VERIFIERS)) -endif - -# Conditional assignments - -ifeq ($(VERIFIER), etherscan) - VERIFIER_URL := https://api.etherscan.io/api - VERIFIER_API_KEY := $(ETHERSCAN_API_KEY) - VERIFIER_PARAMS := --verifier $(VERIFIER) --etherscan-api-key $(ETHERSCAN_API_KEY) -else ifeq ($(VERIFIER), blockscout) - VERIFIER_URL := https://$(BLOCKSCOUT_HOST_NAME)/api\? - VERIFIER_API_KEY := "" - VERIFIER_PARAMS = --verifier $(VERIFIER) --verifier-url "$(VERIFIER_URL)" -else ifeq ($(VERIFIER), sourcify) -else ifeq ($(VERIFIER), zksync) - ifeq ($(CHAIN_ID),300) - VERIFIER_URL := https://explorer.sepolia.era.zksync.dev/contract_verification - else ifeq ($(CHAIN_ID),324) - VERIFIER_URL := https://zksync2-mainnet-explorer.zksync.io/contract_verification - endif - VERIFIER_API_KEY := "" - VERIFIER_PARAMS = --verifier $(VERIFIER) --verifier-url "$(VERIFIER_URL)" -else ifneq ($(filter $(VERIFIER), routescan-mainnet routescan-testnet),) - ifeq ($(VERIFIER), routescan-mainnet) - VERIFIER_URL := https://api.routescan.io/v2/network/mainnet/evm/$(CHAIN_ID)/etherscan - else - VERIFIER_URL := https://api.routescan.io/v2/network/testnet/evm/$(CHAIN_ID)/etherscan - endif - - VERIFIER := custom - VERIFIER_API_KEY := "verifyContract" - VERIFIER_PARAMS = --verifier $(VERIFIER) --verifier-url '$(VERIFIER_URL)' --etherscan-api-key $(VERIFIER_API_KEY) -endif - -# Additional chain-dependent params (Foundry) -ifeq ($(CHAIN_ID),88888) - FORGE_SCRIPT_CUSTOM_PARAMS := --priority-gas-price 1000000000 --gas-price 5200000000000 -else ifeq ($(CHAIN_ID),300) - FORGE_SCRIPT_CUSTOM_PARAMS := --slow - FORGE_BUILD_CUSTOM_PARAMS := --zksync -else ifeq ($(CHAIN_ID),324) - FORGE_SCRIPT_CUSTOM_PARAMS := --slow - FORGE_BUILD_CUSTOM_PARAMS := --zksync -endif - -# TARGETS - -.PHONY: help -help: ## Display the available targets - @echo -e "Available targets:\n" - @cat Makefile | while IFS= read -r line; do \ - if [[ "$$line" == "##" ]]; then \ - echo "" ; \ - elif [[ "$$line" =~ ^##\ (.*)$$ ]]; then \ - printf "\n$${BASH_REMATCH[1]}\n\n" ; \ - elif [[ "$$line" =~ ^([^:]+):(.*)##\ (.*)$$ ]]; then \ - printf "%s %-*s %s\n" "- make" 18 "$${BASH_REMATCH[1]}" "$${BASH_REMATCH[3]}" ; \ - fi ; \ - done - -## - -.PHONY: init -init: ## Check the dependencies and prompt to install if needed - @which forge > /dev/null || curl -L https://foundry.paradigm.xyz | bash - @which lcov > /dev/null || echo "Note: lcov can be installed by running 'sudo apt install lcov'" - @forge build $(FORGE_BUILD_CUSTOM_PARAMS) - -.PHONY: clean -clean: ## Clean the build artifacts - forge clean - rm -f $(TEST_TREE_FILES) - rm -Rf ./out/* lcov.info* ./report/* - -## Testing lifecycle: - -# Run tests faster, locally -test: export ETHERSCAN_API_KEY= - -.PHONY: test -test: ## Run unit tests, locally - forge test $(FORGE_BUILD_CUSTOM_PARAMS) $(VERBOSITY) --no-match-path ./test/fork-tests/*.sol - -.PHONY: test-fork -test-fork: ## Run fork tests, using RPC_URL - forge test $(FORGE_BUILD_CUSTOM_PARAMS) $(VERBOSITY) --match-path ./test/fork-tests/*.sol - -test-coverage: report/index.html ## Generate an HTML coverage report under ./report - @which open > /dev/null && open report/index.html || true - @which xdg-open > /dev/null && xdg-open report/index.html || true - -report/index.html: lcov.info - genhtml $^ -o report - -lcov.info: $(TEST_COVERAGE_SRC_FILES) - forge coverage --report lcov - -## - -sync-tests: $(TEST_TREE_FILES) ## Scaffold or sync test definitions into solidity tests - @for file in $^; do \ - if [ ! -f $${file%.tree}.t.sol ]; then \ - echo "[Scaffold] $${file%.tree}.t.sol" ; \ - bulloak scaffold -s $(SOLC_VERSION) --vm-skip -w $$file ; \ - else \ - echo "[Sync file] $${file%.tree}.t.sol" ; \ - bulloak check --fix $$file ; \ - fi \ - done - - @make test-tree - -check-tests: $(TEST_TREE_FILES) ## Checks if the solidity test files are out of sync - bulloak check $^ - -test-tree: $(TEST_TREE_MARKDOWN) ## Generates a markdown file with the test definitions - -# Generate single a markdown file with the test trees -$(TEST_TREE_MARKDOWN): $(TEST_TREE_FILES) - @echo "[Markdown] $(@)" - @echo "# Test tree definitions" > $@ - @echo "" >> $@ - @echo "Below is the graphical summary of the tests described within [test/*.t.yaml](./test)" >> $@ - @echo "" >> $@ - - @for file in $^; do \ - echo "\`\`\`" >> $@ ; \ - cat $$file >> $@ ; \ - echo "\`\`\`" >> $@ ; \ - done - -# Internal dependencies and transformations - -$(TEST_TREE_FILES): $(TEST_SOURCE_FILES) - -%.tree: %.t.yaml - @if ! command -v deno >/dev/null 2>&1; then \ - echo "Note: deno can be installed by running 'curl -fsSL https://deno.land/install.sh | sh'" ; \ - exit 1 ; \ - fi - @if ! command -v bulloak >/dev/null 2>&1; then \ - echo "Note: bulloak can be installed by running 'cargo install bulloak'" ; \ - exit 1 ; \ - fi - - @for file in $^; do \ - echo "[Convert] $$file -> $${file%.t.yaml}.tree" ; \ - cat $$file | $(MAKE_TEST_TREE_CMD) > $${file%.t.yaml}.tree ; \ - done - -# LLM prompt generation - -test-tree-prompt: export PROMPT_TEMPLATE=$(TEST_TREE_GENERATION_PROMPT) - -.PHONY: test-tree-prompt -test-tree-prompt: ## Prints an LLM prompt to generate the test definitions for a given file - @if [ -z "$(src)" ] ; then \ - echo "Usage:" ; \ - echo " $$ make $(@) src=./path/to/reference-file" ; \ - echo ; \ - exit 1 ; \ - fi - @stat $(src) > /dev/null - @/bin/bash -c 'printf "%s\n" "$$PROMPT_TEMPLATE"' - @echo - @echo "\`\`\`" - @cat $(src) - @echo "\`\`\`" - -test-prompt: export PROMPT_TEMPLATE=$(TEST_FILE_GENERATION_PROMPT) -test-prompt: CONTRACT_FILES=$(wildcard src/**/*.sol) -test-prompt: DAO_BUILDER=test/builders/SimpleBuilder.sol -test-prompt: TEST_BASE=test/lib/TestBase.sol - -.PHONY: test-prompt -test-prompt: ## Prints an LLM prompt to implement the tests for a given contract - @if [ -z "$(def)" ] || [ -z "$(src)" ] ; then \ - echo "Usage:" ; \ - echo " $$ make $(@) def=./MyContract.t.yaml src=./MyContract.t.sol" ; \ - echo " $$ make $(@) def=./MyContract.t.yaml src=./MyContract.t.sol ref=./ExistingTest.ts" ; \ - echo ; \ - exit 1 ; \ - fi - @printf '%s' "$$PROMPT_TEMPLATE" | awk \ - -v sources="$(CONTRACT_FILES)" \ - -v dao_builder_file="$(DAO_BUILDER)" \ - -v test_base_file="$(TEST_BASE)" \ - -v test_tree_file="$(def)" \ - -v target_test_file="$(src)" \ - -v opt_reference_file="$(ref)" \ - ' \ - function readfile(filename) { \ - while ((getline line < filename) > 0) { \ - print line; \ - } \ - close(filename); \ - } \ - BEGIN { \ - split(sources, source_files, " "); \ - } \ - /<>/ { \ - for (i in source_files) { \ - readfile(source_files[i]); \ - } \ - next; \ - } \ - /<>/ { \ - readfile(dao_builder_file); \ - next; \ - } \ - /<>/ { \ - readfile(test_base_file); \ - next; \ - } \ - /<>/ { \ - readfile(test_tree_file); \ - next; \ - } \ - /<>/ { \ - if (opt_reference_file != "") { \ - readfile(opt_reference_file); \ - } \ - next; \ - } \ - /<>/ { \ - readfile(target_test_file); \ - next; \ - } \ - { print; } \ - ' - -## Metadata targets: - -.PHONY: pin-metadata - -pin-metadata: ## Uploads and pins the release/build metadata on IPFS - @if ! command -v deno >/dev/null 2>&1; then \ - echo "Note: deno can be installed by running 'curl -fsSL https://deno.land/install.sh | sh'" ; \ - exit 1 ; \ - fi - @echo "Uploading build-metadata.json..." - @echo ipfs://$$(deno run --allow-read --allow-env --allow-net script/ipfs-pin.ts script/metadata/build-metadata.json) - @echo - @echo "Uploading release-metadata.json..." - @echo ipfs://$$(deno run --allow-read --allow-env --allow-net script/ipfs-pin.ts script/metadata/release-metadata.json) - -.PHONY: script/metadata/upgrade-proposal-metadata.json -script/metadata/upgrade-proposal-metadata.json: broadcast/$(DEPLOYMENT_SCRIPT).s.sol/$(CHAIN_ID)/run-latest.json - @if [ "$(CHAIN_ID)" == "300" -o "$(CHAIN_ID)" == "324" ]; then \ - echo "On ZkSync, you need to manually define the PluginSetup adress passed to EncodeUpgradeProposal.sol" ; \ - exit 1; \ - fi - @PLUGIN_SETUP=$$(cat $(<) | jq ".transactions[2].contractAddress" | xargs echo) && \ - cat script/metadata/upgrade-proposal-metadata-template.json \ - | sed "s/___PLUGIN_SETUP___/$$PLUGIN_SETUP/g" \ - | sed "s/___PLUGIN_REPO___/$(PLUGIN_REPO_ADDRESS)/g" \ - | sed "s/___RELEASE_METADATA___/$(subst /,\/,$(subst ',,$(subst ",,$(RELEASE_METADATA_URI))))/g" \ - | sed "s/___BUILD_METADATA___/$(subst /,\/,$(subst ',,$(subst ",,$(BUILD_METADATA_URI))))/g" \ - > $(@) - -## Deployment targets: - -predeploy: export SIMULATION=true - -.PHONY: predeploy -predeploy: ## Simulate a protocol deployment - @echo "Simulating the deployment" - forge script $(DEPLOYMENT_SCRIPT_PARAM) \ - --rpc-url $(RPC_URL) \ - $(FORGE_BUILD_CUSTOM_PARAMS) \ - $(FORGE_SCRIPT_CUSTOM_PARAMS) \ - $(VERBOSITY) - -.PHONY: deploy -deploy: test ## Deploy the protocol, verify the source code and write to ./artifacts - @echo "Starting the deployment" - @mkdir -p $(LOGS_FOLDER) $(ARTIFACTS_FOLDER) - forge script $(DEPLOYMENT_SCRIPT_PARAM) \ - --rpc-url $(RPC_URL) \ - --retries 10 \ - --delay 8 \ - --broadcast \ - --verify \ - $(VERIFIER_PARAMS) \ - $(FORGE_BUILD_CUSTOM_PARAMS) \ - $(FORGE_SCRIPT_CUSTOM_PARAMS) \ - $(VERBOSITY) 2>&1 | tee -a $(LOGS_FOLDER)/$(DEPLOYMENT_LOG_FILE) - -.PHONY: resume -resume: test ## Retry pending deployment transactions, verify the code and write to ./artifacts - @echo "Retrying the deployment" - @mkdir -p $(LOGS_FOLDER) $(ARTIFACTS_FOLDER) - forge script $(DEPLOYMENT_SCRIPT_PARAM) \ - --rpc-url $(RPC_URL) \ - --retries 10 \ - --delay 8 \ - --broadcast \ - --verify \ - --resume \ - $(VERIFIER_PARAMS) \ - $(FORGE_BUILD_CUSTOM_PARAMS) \ - $(FORGE_SCRIPT_CUSTOM_PARAMS) \ - $(VERBOSITY) 2>&1 | tee -a $(LOGS_FOLDER)/$(DEPLOYMENT_LOG_FILE) - -## Upgrade proposal: - -.PHONY: upgrade-proposal -upgrade-proposal: script/metadata/upgrade-proposal-metadata.json ## Encodes and shows the calldata to create the upgrade proposal - @if [ "$(CHAIN_ID)" == "300" -o "$(CHAIN_ID)" == "324" ]; then \ - echo "On ZkSync, you need to manually define the PluginSetup adress passed to EncodeUpgradeProposal.sol" ; \ - exit 1; \ - fi - PLUGIN_SETUP=$$(cat broadcast/$(DEPLOYMENT_SCRIPT).s.sol/$(CHAIN_ID)/run-latest.json | jq ".transactions[2].contractAddress" | xargs echo) \ - PROPOSAL_METADATA_URI=ipfs://$$(deno run --allow-read --allow-env --allow-net script/ipfs-pin.ts $(<)) \ - TIMESTAMP=$$(deno eval "console.log(Math.floor(Date.now()/1000))") \ - forge script script/EncodeUpgradeProposal.sol - -## Verification: - -.PHONY: verify-etherscan -verify-etherscan: broadcast/$(DEPLOYMENT_SCRIPT).s.sol/$(CHAIN_ID)/run-latest.json ## Verify the last deployment on an Etherscan (compatible) explorer - forge build $(FORGE_BUILD_CUSTOM_PARAMS) - bash $(VERIFY_CONTRACTS_SCRIPT) $(CHAIN_ID) $(VERIFIER) $(VERIFIER_URL) $(VERIFIER_API_KEY) - -.PHONY: verify-blockscout -verify-blockscout: broadcast/$(DEPLOYMENT_SCRIPT).s.sol/$(CHAIN_ID)/run-latest.json ## Verify the last deployment on BlockScout - forge build $(FORGE_BUILD_CUSTOM_PARAMS) - bash $(VERIFY_CONTRACTS_SCRIPT) $(CHAIN_ID) $(VERIFIER) https://$(BLOCKSCOUT_HOST_NAME)/api $(VERIFIER_API_KEY) - -.PHONY: verify-sourcify -verify-sourcify: broadcast/$(DEPLOYMENT_SCRIPT).s.sol/$(CHAIN_ID)/run-latest.json ## Verify the last deployment on Sourcify - forge build $(FORGE_BUILD_CUSTOM_PARAMS) - bash $(VERIFY_CONTRACTS_SCRIPT) $(CHAIN_ID) $(VERIFIER) "" "" - -## - -.PHONY: refund -refund: ## Refund the remaining balance left on the deployment account - @echo "Refunding the remaining balance on $(DEPLOYMENT_ADDRESS)" - @if [ -z $(REFUND_ADDRESS) -o $(REFUND_ADDRESS) = "0x0000000000000000000000000000000000000000" ]; then \ - echo "- The refund address is empty" ; \ - exit 1; \ - fi - @BALANCE=$(shell cast balance $(DEPLOYMENT_ADDRESS) --rpc-url $(RPC_URL)) && \ - GAS_PRICE=$(shell cast gas-price --rpc-url $(RPC_URL)) && \ - REMAINING=$$(echo "$$BALANCE - $$GAS_PRICE * 21000" | bc) && \ - \ - ENOUGH_BALANCE=$$(echo "$$REMAINING > 0" | bc) && \ - if [ "$$ENOUGH_BALANCE" = "0" ]; then \ - echo -e "- No balance can be refunded: $$BALANCE wei\n- Minimum balance: $${REMAINING:1} wei" ; \ - exit 1; \ - fi ; \ - echo -n -e "Summary:\n- Refunding: $$REMAINING (wei)\n- Recipient: $(REFUND_ADDRESS)\n\nContinue? (y/N) " && \ - \ - read CONFIRM && \ - if [ "$$CONFIRM" != "y" ]; then echo "Aborting" ; exit 1; fi ; \ - \ - cast send --private-key $(DEPLOYMENT_PRIVATE_KEY) \ - --rpc-url $(RPC_URL) \ - --value $$REMAINING \ - $(REFUND_ADDRESS) - -# Other: Troubleshooting and helpers - -.PHONY: gas-price -gas-price: - @echo "Gas price ($(NETWORK_NAME)):" - @cast gas-price --rpc-url $(RPC_URL) - -.PHONY: balance -balance: - @echo "Balance of $(DEPLOYMENT_ADDRESS) ($(NETWORK_NAME)):" - @BALANCE=$$(cast balance $(DEPLOYMENT_ADDRESS) --rpc-url $(RPC_URL)) && \ - cast --to-unit $$BALANCE ether - -.PHONY: clean-nonces -clean-nonces: - for nonce in $(nonces); do \ - make clean-nonce nonce=$$nonce ; \ - done - -.PHONY: clean-nonce -clean-nonce: - cast send --private-key $(DEPLOYMENT_PRIVATE_KEY) \ - --rpc-url $(RPC_URL) \ - --value 0 \ - --nonce $(nonce) \ - $(DEPLOYMENT_ADDRESS) diff --git a/README.md b/README.md index d22db11d..9535aadb 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,11 @@ Other features: ## Project structure ``` -├── Makefile +├── justfile # task launcher (imports lib/just-foundry) ├── foundry.toml ├── remappings.txt +├── lib +│ └── just-foundry # shared just recipes + network configs (submodule) ├── npm-artifacts │ └── src │ ├── abi.ts @@ -67,7 +69,7 @@ Other features: ├── script │ ├── DeployNewTokenVotingRepo.s.sol │ ├── DeployTokenVoting_1_4.s.sol -│ ├── make-test-tree.ts +│ ├── EncodeUpgradeProposal.sol │ └── verify-contracts.sh ├── src │ ├── TokenVoting.sol @@ -88,139 +90,83 @@ Other features: ## Prerequisites - [Foundry](https://getfoundry.sh/) -- [Make](https://www.gnu.org/software/make/) +- [just](https://github.com/casey/just) Optional: - [Docker](https://www.docker.com) (recommended for deploying) -- [Deno](https://deno.land) (used to scaffold the test files) ## Getting Started -To get started, clone this repository and initialize it: +Clone the repository with submodules and initialize it for a network: ```bash -cp .env.example .env -make init +git clone --recurse-submodules +just init # defaults to mainnet, e.g. `just init sepolia` ``` -Edit `.env` to match your desired network and settings. +`just init` fetches submodules, creates `.env` from `.env.example`, and selects the network. Edit `.env` to add your secrets (`DEPLOYER_KEY`, `ETHERSCAN_API_KEY`, `PINATA_JWT`, …). Alternatively, resolve them with the [`vars`](https://github.com/vars-cli/vars) secret manager — the keys this project needs are declared in [`.vars.yaml`](./.vars.yaml), and just-foundry's recipes call `vars resolve` automatically when `vars` is installed. -### Using the Makefile +Network settings (RPC URL, chain id, verifier, and the Aragon OSx addresses) come from `lib/just-foundry/networks/.env` — switch with `just switch `, inspect the resolved values with `just env`, and create a local editable copy with `just switch override`. -The `Makefile` is the target launcher of the project. It's the recommended way to operate the repository. It manages the env variables of common tasks and executes only the steps that need to be run. +### Using just -``` -$ make -Available targets: - -- make help Display the available targets - -- make init Check the dependencies and prompt to install if needed -- make clean Clean the build artifacts - -Testing lifecycle: - -- make test Run unit tests, locally -- make test-fork Run fork tests, using RPC_URL -- make test-coverage Generate an HTML coverage report under ./report - -- make sync-tests Scaffold or sync test definitions into solidity tests -- make check-tests Checks if the solidity test files are out of sync -- make test-tree Generates a markdown file with the test definitions -- make test-tree-prompt Prints an LLM prompt to generate the test definitions for a given file -- make test-prompt Prints an LLM prompt to implement the tests for a given contract - -Metadata targets: - -- make pin-metadata Uploads and pins the release/build metadata on IPFS - -Deployment targets: - -- make predeploy Simulate a protocol deployment -- make deploy Deploy the protocol, verify the source code and write to ./artifacts -- make resume Retry pending deployment transactions, verify the code and write to ./artifacts - -Upgrade proposal: - -- make upgrade-proposal Encodes and shows the calldata to create the upgrade proposal - -Verification: - -- make verify-etherscan Verify the last deployment on an Etherscan (compatible) explorer -- make verify-blockscout Verify the last deployment on BlockScout -- make verify-sourcify Verify the last deployment on Sourcify - -- make refund Refund the remaining balance left on the deployment account -``` - -## Testing - -Using `make`: +`just` is the task launcher for the project; the generic recipes are imported from [`lib/just-foundry`](https://github.com/aragon/just-foundry). Run `just` (or `just help`) to list them: ``` -$ make -[...] -Testing lifecycle: - -- make test Run unit tests, locally -- make test-fork Run fork tests, using RPC_URL -- make test-coverage Generate an HTML coverage report under ./report +$ just +[setup] +init network="mainnet" Fetch submodules, scaffold .env and select the network +switch network override="" Select the active network +setup Install Foundry + +[metadata] +pin-metadata Pin the release & build metadata to IPFS +upgrade-proposal Encode & print the calldata to create the upgrade proposal + +[script] +predeploy Dry-run the deploy script (no broadcast) +deploy *args Run tests, then broadcast + verify + +[test] +test *args Run unit tests (fork tests excluded) +test-fork *args Run fork tests (requires RPC_URL) +test-coverage Generate an HTML coverage report under ./report + +[verification] +verify type="" script="" Verify the latest broadcast (etherscan|blockscout|sourcify) + +[develop] +clean Clean build artifacts and reports +storage-info contract Show a contract's storage layout +check-upgrade from to Check storage-layout upgrade compatibility + +[helpers] +env Show the resolved environment (values + sources) ``` -Run `make test` or `make test-fork` to check the logic's accordance to the specs. The latter will require `RPC_URL` to be defined. - -### Writing tests - -Optionally, tests with hierarchies can be described using yaml files like [MyPlugin.t.yaml](./test/MyPlugin.t.yaml), which will be transformed into solidity files by running `make sync-tests`, thanks to [bulloak](https://github.com/alexfertel/bulloak). - -Create a file with `.t.yaml` extension within the `test` folder and describe a hierarchy using the following structure: - -```yaml -# MyPlugin.t.yaml - -MyPluginTest: - - given: The caller has no permission - comment: The caller needs MANAGER_PERMISSION_ID - and: - - when: Calling setNumber() - then: - - it: Should revert - - given: The caller has permission - and: - - when: Calling setNumber() - then: - - it: It should update the stored number - - when: Calling number() - then: - - it: Should return the right value -``` +There are also `balance`, `refund`, `gas-price`, `nonce` and `clean-nonce` deployer helpers (run `just `). -Nodes like `when` and `given` can be nested without limitations. +## Testing -Then use `make sync-tests` to automatically sync the described branches into solidity test files. +Run the suites with `just`: ```sh -$ make -Testing lifecycle: -# ... - -- make sync-tests Scaffold or sync test definitions into solidity tests -- make check-tests Checks if the solidity test files are out of sync -- make test-tree Generates a markdown file with the test definitions - -$ make sync-tests +just test # unit tests (fork tests excluded) +just test-fork # fork tests (requires a reachable RPC_URL for the active network) +just test-coverage # HTML coverage report under ./report ``` -Each yaml file generates (or syncs) a solidity test file with functions ready to be implemented. They also generate a human readable summary in [TESTS.md](./TESTS.md). +`just test` checks the logic's accordance to the specs; `just test-fork` additionally requires `RPC_URL` (from the selected network or `.env`). ## Deployment 🚀 -Check the available make targets to simulate and deploy the smart contracts: +Select the target network, then simulate and deploy: -``` -- make predeploy Simulate a protocol deployment -- make deploy Deploy the protocol and verify the source code +```sh +just switch +just predeploy # simulate (no broadcast) +just deploy # run tests, then broadcast + verify; logs to ./logs ``` ### Deployment Checklist @@ -230,7 +176,8 @@ When running a production deployment ceremony, you can use these steps as a refe - [ ] I have cloned the official repository on my computer and I have checked out the `main` branch - [ ] I am using the latest official docker engine, running a Debian Linux (stable) image - [ ] I have run `docker run --rm -it -v .:/deployment debian:bookworm-slim` - - [ ] I have run `apt update && apt install -y make curl git vim neovim bc` + - [ ] I have run `apt update && apt install -y curl git vim neovim bc` + - [ ] I have installed `just` (`curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin`) - On **standard EVM networks**: - [ ] I have run `curl -L https://foundry.paradigm.xyz | bash` - [ ] I have run `source /root/.bashrc` @@ -240,18 +187,16 @@ When running a production deployment ceremony, you can use these steps as a refe - [ ] I have run `source /root/.bashrc` - [ ] I have run `foundryup-zksync` - [ ] I have run `cd /deployment` - - [ ] I have run `cp .env.example .env` - - [ ] I have run `make init` + - [ ] I have run `just init ` - [ ] I am opening an editor on the `/deployment` folder, within the Docker container - [ ] The `.env` file contains the correct parameters for the deployment - - [ ] I have created a new burner wallet with `cast wallet new` and copied the private key to `DEPLOYMENT_PRIVATE_KEY` within `.env` - - [ ] I have set the correct `RPC_URL` for the network - - [ ] I have set the correct `CHAIN_ID` for the network + - [ ] I have created a new burner wallet with `cast wallet new` and copied the private key to `DEPLOYER_KEY` within `.env` + - [ ] I have selected the correct network with `just switch ` (this sets RPC_URL, CHAIN_ID and the verifier) - [ ] I have set `ETHERSCAN_API_KEY` or `BLOCKSCOUT_HOST_NAME` (when relevant to the target network) - [ ] (TO DO: Add a step to check your own variables here) - - [ ] I have printed the contents of `.env` to the screen + - [ ] I have run `just env` and confirmed the resolved values - [ ] I am the only person of the ceremony that will operate the deployment wallet -- [ ] All the tests run clean (`make test`) +- [ ] All the tests run clean (`just test`) - My computer: - [ ] Is running in a safe location and using a trusted network - [ ] It exposes no services or ports @@ -259,52 +204,48 @@ When running a production deployment ceremony, you can use these steps as a refe - Linux: `netstat -tulpn` - Windows: `netstat -nao -p tcp` - [ ] The wifi or wired network in use does not expose any ports to a WAN -- [ ] I have run `make predeploy` and the simulation completes with no errors +- [ ] I have run `just predeploy` and the simulation completes with no errors - [ ] The deployment wallet has sufficient native token for gas - At least, 15% more than the amount estimated during the simulation -- [ ] `make test` still runs clean +- [ ] `just test` still runs clean - [ ] I have run `git status` and it reports no local changes - [ ] The current local git branch (`main`) corresponds to its counterpart on `origin` - [ ] I confirm that the rest of members of the ceremony pulled the last git commit on `main` and reported the same commit hash as my output for `git log -n 1` -- [ ] I have initiated the production deployment with `make deploy` +- [ ] I have initiated the production deployment with `just deploy` ### Post deployment checklist - [ ] The deployment process completed with no errors - [ ] The factory contract was deployed by the deployment address - [ ] All the project's smart contracts are correctly verified on the reference block explorer of the target network. -- [ ] The output of the latest `logs/deployment--.log` file corresponds to the console output +- [ ] The output of the latest `logs/