Skip to content

Commit

Permalink
feat: add upgrade scripts (#94)
Browse files Browse the repository at this point in the history
* feat: add utils dependency

* refactor: remove unused JsonReader.sol

* feat: add upgrade script

* feat: set dumper as fee recipient

* refactor: script in helpers

* refactor: revert to JsonReader to fix build issue

* feat: bump utils

* chore: ignore forked Base test for ci

* fix: package.json foundry:test script
  • Loading branch information
nlecoufl authored Dec 20, 2024
1 parent 9135657 commit f72f358
Show file tree
Hide file tree
Showing 16 changed files with 352 additions and 144 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ bin

# foundry
/out
/zkout
/cache-forge
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion contracts/Disputer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
▓▓▓ ▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
*/

pragma solidity 0.8.24;
pragma solidity ^0.8.24;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
Expand Down
28 changes: 15 additions & 13 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ auto_detect_solc = false
src = 'contracts'
out = 'out'
test = 'test'
libs = ["node_modules", "lib"]
libs = ["node_modules"]
script = 'scripts'
cache_path = 'cache-forge'
gas_reports = ["*"]
optimizer_runs = 100
fs_permissions = [{ access = "read", path = "./node_modules/@angleprotocol/sdk/dist/src/registry/registry.json" }]
solc = "0.8.24"
fs_permissions = [
{ access = "read", path = "./node_modules/@angleprotocol/sdk/dist/src/registry/registry.json" },
{ access = "write", path = "./transaction.json" },
{ access = "write", path = "./transactions.json" }
]
solc = "0.8.25"

ffi = true

Expand Down Expand Up @@ -39,7 +43,6 @@ bob = "${BOB_NODE_URI}"
linea = "${LINEA_NODE_URI}"
zksync = "${ZKSYNC_NODE_URI}"
mantle = "${MANTLE_NODE_URI}"
filecoin = "${FILECOIN_NODE_URI}"
blast = "${BLAST_NODE_URI}"
mode = "${MODE_NODE_URI}"
thundercore = "${THUNDERCORE_NODE_URI}"
Expand All @@ -66,13 +69,13 @@ swell = "${SWELL_NODE_URI}"
fork = "${ETH_NODE_URI_FORK}"

[etherscan]
localhost = { url = "http://localhost:4000", key = "${LOCALHOST_ETHERSCAN_API_KEY}" }
localhost = { url = "http://localhost:4000", key = "none" }
mainnet = { chainId = 1, key = "${MAINNET_ETHERSCAN_API_KEY}", url = "https://api.etherscan.io/api" }
polygon = { chainId = 137, key = "${POLYGON_ETHERSCAN_API_KEY}", url = "https://api.polygonscan.com/api" }
fantom = { chainId = 250, key = "${FANTOM_ETHERSCAN_API_KEY}", url = "https://api.ftmscan.com/api" }
optimism = { chainId = 10, key = "${OPTIMISM_ETHERSCAN_API_KEY}", url = "https://api-optimistic.etherscan.io/api" }
arbitrum = { chainId = 42161, key = "${ARBITRUM_ETHERSCAN_API_KEY}", url = "https://api.arbiscan.io/api" }
avalanche = { chainId = 43114, key = "${AVALANCHE_ETHERSCAN_API_KEY}", url = "api.avascan.info/v2/network/mainnet/evm/43114/etherscan" }
avalanche = { chainId = 43114, key = "${AVALANCHE_ETHERSCAN_API_KEY}", url = "https://api.avascan.info/v2/network/mainnet/evm/43114/etherscan" }
aurora = { chainId = 1313161554, key = "${AURORA_ETHERSCAN_API_KEY}", url = "http://localhost:4000" }
bsc = { chainId = 56, key = "${BSC_ETHERSCAN_API_KEY}", url = "https://api.bscscan.com/api" }
gnosis = { chainId = 100, key = "${GNOSIS_ETHERSCAN_API_KEY}", url = "https://api.gnosisscan.io/api" }
Expand All @@ -82,7 +85,6 @@ bob = { chainId = 60808, key = "${BOB_ETHERSCAN_API_KEY}", url = "https://explor
linea = { chainId = 59144, key = "${LINEA_ETHERSCAN_API_KEY}", url = "https://api.lineascan.build/api" }
zksync = { chainId = 324, key = "${ZKSYNC_ETHERSCAN_API_KEY}", url = "https://explorer.sepolia.era.zksync.dev/contract_verification" }
mantle = { chainId = 5000, key = "${MANTLE_ETHERSCAN_API_KEY}", url = "https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan" }
filecoin = { chainId = 314, key = "${FILECOIN_ETHERSCAN_API_KEY}", url = "" }
blast = { chainId = 81457, key = "${BLAST_ETHERSCAN_API_KEY}", url = "https://api.blastscan.io/api" }
mode = { chainId = 34443, key = "${MODE_ETHERSCAN_API_KEY}", url = "https://api.routescan.io/v2/network/mainnet/evm/34443/etherscan/api" }
thundercore = { chainId = 108, key = "${THUNDERCORE_ETHERSCAN_API_KEY}", url = "" }
Expand All @@ -92,14 +94,14 @@ taiko = { chainId = 167000, key = "${TAIKO_ETHERSCAN_API_KEY}", url = "https://a
fuse = { chainId = 122, key = "${FUSE_ETHERSCAN_API_KEY}", url = "https://explorer.fuse.io/api" }
immutable = { chainId = 13371, key = "${IMMUTABLE_ETHERSCAN_API_KEY}", url = "https://immutable-mainnet.blockscout.com/api" }
scroll = { chainId = 534352, key = "${SCROLL_ETHERSCAN_API_KEY}", url = "https://api.scrollscan.com/api" }
manta = { chainId = 169, key = "${MANTA_ETHERSCAN_API_KEY}", url = "" }
sei = { chainId = 1329, key = "${SEI_ETHERSCAN_API_KEY}", url = "" }
manta = { chainId = 169, key = "${MANTA_ETHERSCAN_API_KEY}", url = "https://pacific-explorer.manta.network/api" }
sei = { chainId = 1329, key = "${SEI_ETHERSCAN_API_KEY}", url = "https://seitrace.com/pacific-1/api" }
celo = { chainId = 42220, key = "${CELO_ETHERSCAN_API_KEY}", url = "https://api.celoscan.io/api" }
fraxtal = { chainId = 252, key = "${FRAXTAL_ETHERSCAN_API_KEY}", url = "https://api.fraxscan.io/api" }
astar = { chainId = 592, key = "${ASTAR_ETHERSCAN_API_KEY}", url = "" }
astarzkevm = { chainId = 3776, key = "${ASTARZKEVM_ETHERSCAN_API_KEY}", url = "" }
rootstock = { chainId = 30, key = "${ROOTSTOCK_ETHERSCAN_API_KEY}", url = "" }
moonbeam = { chainId = 1284, key = "${MOONBEAM_ETHERSCAN_API_KEY}", url = "" }
astar = { chainId = 592, key = "${ASTAR_ETHERSCAN_API_KEY}", url = "https://astar.blockscout.com/api/" }
astarzkevm = { chainId = 3776, key = "${ASTARZKEVM_ETHERSCAN_API_KEY}", url = "https://astar-zkevm.explorer.startale.com/api" }
rootstock = { chainId = 30, key = "${ROOTSTOCK_ETHERSCAN_API_KEY}", url = "https://rootstock.blockscout.com/api/" }
moonbeam = { chainId = 1284, key = "${MOONBEAM_ETHERSCAN_API_KEY}", url = "https://api-moonbase.moonscan.io/api" }
skale = { chainId = 2046399126, key = "${SKALE_ETHERSCAN_API_KEY}", url = "https://internal-hubs.explorer.mainnet.skalenodes.com:10001/api" }
worldchain = { chainId = 480, key = "${WORLDCHAIN_ETHERSCAN_API_KEY}", url = "https://worldchain-mainnet.explorer.alchemy.com/api" }
lisk = { chainId = 1135, key = "${LISK_ETHERSCAN_API_KEY}", url = "https://blockscout.lisk.com/api/" }
Expand Down
228 changes: 228 additions & 0 deletions helpers/foundryMultiChainScript.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#! /bin/bash

function usage {
echo "bash foundryMultiChainScript.sh <foundry-script-path>"
echo "Lists all chains where Merkl DistributionCreator is deployed and allows selection"
echo "Example: bash foundryMultiChainScript.sh ./scripts/DistributionCreator.s.sol:UpgradeAndBuildUpgradeToPayload"
echo ""
}

# Get list of chain IDs where DistributionCreator is deployed
function get_available_chains() {
local registry_file="node_modules/@angleprotocol/sdk/dist/src/registry/registry.json"
if [ ! -f "$registry_file" ]; then
echo "Registry file not found!"
exit 1
fi

jq -r 'to_entries | .[] | select(.value.Merkl.DistributionCreator != null) | .key' "$registry_file"
}

# Get list of chains to deploy to, handling exclusions
function get_selected_chains() {
local chain_ids=("$@")
local selected_chains=()
local exclude_chain_ids=(314) # Default exclusions: filecoin

read -p "Do you want to run the script on all chains? (y/n) -- Note: ChainIDs 314 and 324 is already excluded by default: " deploy_all

if [[ "$deploy_all" == "y" ]]; then
for chain_id in "${chain_ids[@]}"; do
if [[ ! " ${exclude_chain_ids[@]} " =~ " ${chain_id} " ]]; then
selected_chains+=("$chain_id")
fi
done
else
read -p "Enter chain IDs to exclude (space-separated), or press enter to continue: " -a additional_exclude
exclude_chain_ids+=("${additional_exclude[@]}")

for chain_id in "${chain_ids[@]}"; do
if [[ ! " ${exclude_chain_ids[@]} " =~ " ${chain_id} " ]]; then
selected_chains+=("$chain_id")
fi
done
fi

printf "%s " "${selected_chains[@]}"
}

# Get verification string for a specific chain
function get_verify_string() {
local chain_id=$1
local verify_string=""

local verifier_type_var="VERIFIER_TYPE_${chain_id}"
local verifier_type=$(eval "echo \$$verifier_type_var")

if [ ! -z "${verifier_type}" ]; then
verify_string="--verify --verifier ${verifier_type}"

# Add verifier URL if present
local verifier_url_var="VERIFIER_URL_${chain_id}"
local verifier_url=$(eval "echo \$$verifier_url_var")
if [ ! -z "${verifier_url}" ]; then
verify_string="${verify_string} --verifier-url ${verifier_url}"
fi

# Add API key if present
local verifier_api_key_var="VERIFIER_API_KEY_${chain_id}"
local verifier_api_key=$(eval "echo \$$verifier_api_key_var")
if [ ! -z "${verifier_api_key}" ]; then
if [ "${verifier_type}" == "etherscan" ]; then
verify_string="${verify_string} --etherscan-api-key ${verifier_api_key}"
else
verify_string="${verify_string} --verifier-api-key ${verifier_api_key}"
fi
fi
fi

echo "$verify_string"
}

# Get compilation flags for a specific chain
function get_compile_flags() {
local chain_id=$1

london_chain_ids=(30 122 592 1284 1923 10242 108 250 42220 59144)
legacy_chain_ids=(196 250 1329 3776 480 2046399126 42793)
zk_chain_ids=(324)
if [[ " ${london_chain_ids[@]} " =~ " ${chain_id} " ]]; then
echo "--evm-version london"
elif [[ " ${legacy_chain_ids[@]} " =~ " ${chain_id} " ]]; then
echo "--legacy"
elif [[ " ${zk_chain_ids[@]} " =~ " ${chain_id} " ]]; then
echo "--zksync"
else
echo ""
fi
}

function main {
# Check if script path is provided
if [ -z "$1" ]; then
usage
exit 1
fi

FOUNDRY_SCRIPT="$1"

# Verify the script exists
if [ ! -f "$FOUNDRY_SCRIPT" ]; then
echo "Error: Script file '$FOUNDRY_SCRIPT' not found!"
exit 1
fi

# Path to the registry file
registry_file="node_modules/@angleprotocol/sdk/dist/src/registry/registry.json"

if [ ! -f "$registry_file" ]; then
echo "Registry file not found!"
exit 1
fi


# Store chain IDs in an array
chain_ids=()
while IFS= read -r chain_id; do
chain_ids+=("$chain_id")
done <<< "$(jq -r 'to_entries | .[] | select(.value.Merkl.DistributionCreator != null) | .key' "$registry_file")"

# Display all chains
echo "Chain IDs where Merkl DistributionCreator is deployed: ${chain_ids[@]}"

echo ""
selected_chains=($(get_selected_chains "${chain_ids[@]}"))

source .env
rm -f ./transaction.json

# Initialize arrays for tracking deployment status
successful_chains=()
failed_chains=()

# Prompt user for broadcast and verify options
read -p "Do you want to broadcast the transaction? (y/n): " broadcast_choice

# Set flags based on user input
if [ "$broadcast_choice" == "y" ]; then
broadcast_flag="--broadcast"
read -p "Do you want to verify the transaction? (y/n): " verify_choice
else
broadcast_flag=""
fi

# Run forge script for each selected chain
for chain_id in "${selected_chains[@]}"; do
echo "Running forge script for chain ID: $chain_id"
rpc_url_var="ETH_NODE_URI_${chain_id}"
rpc_url=$(eval "echo \$$rpc_url_var")

# Check if chain ID already exists in transactions.json
if [ -f "./transactions.json" ] && jq -e "has(\"$chain_id\")" ./transactions.json > /dev/null; then
echo "Chain ID $chain_id already exists in transactions.json, skipping..."
continue
fi

# Verification string based on chain-specific environment variables
if [ "$verify_choice" == "y" ]; then
verify_string=$(get_verify_string "$chain_id")
else
verify_string=""
fi

# Compilation specific flags
compile_flags=$(get_compile_flags "$chain_id")

cmd="forge script $FOUNDRY_SCRIPT $broadcast_flag --rpc-url $rpc_url $compile_flags $verify_string --force"
echo "Running command: $cmd"
if eval $cmd && [ -f "./transaction.json" ]; then
successful_chains+=("$chain_id")
else
failed_chains+=("$chain_id")
fi

# Create a new JSON object with chain ID as key and transaction data as value
if [ -f "./transaction.json" ]; then
jq -s '.[0] * {("'$chain_id'"): .[1]}' \
./transactions.json \
./transaction.json > ./transactions.json.tmp

mv ./transactions.json.tmp ./transactions.json
rm -f ./transaction.json
fi

# Add verification step if verification was requested
if [ "$verify_choice" == "y" ]; then
echo "Attempting contract verification..."
# Extract contract address from the data field (removing 0x3659cfe6 prefix and any leading zeros)
contract_address=$(jq -r --arg chainid "$chain_id" '.[$chainid].data' ./transactions.json | sed 's/^0x3659cfe6000000000000000000000000//')

if [ ! -z "$contract_address" ] && [ "$contract_address" != "null" ]; then
# Get verification parameters from environment variables
verify_flag=$(get_verify_string "$chain_id" | sed 's/--verify //')
compile_flags=$(get_compile_flags "$chain_id" | sed 's/--legacy //')
verify_cmd="forge verify-contract --rpc-url $rpc_url 0x$contract_address contracts/DistributionCreator.sol:DistributionCreator $verify_flag $compile_flags --watch"
echo "Running verification command: $verify_cmd"
if eval $verify_cmd; then
echo "✅ Contract verification successful"
else
echo "❌ Contract verification failed"
fi
fi
fi

echo "Safe to cancel job for 5 seconds"
sleep 5
echo "Starting next chain"
done

# Display final deployment status
if [ ${#successful_chains[@]} -gt 0 ]; then
echo -e "\n✅ Deployment successful on chains: ${successful_chains[*]}"
fi
if [ ${#failed_chains[@]} -gt 0 ]; then
echo -e "\n❌ Deployment issues on chains: ${failed_chains[*]}"
fi
}

main "$@"
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"foundry:run": "docker run -it --rm -v $(pwd):/app -w /app ghcr.io/foundry-rs/foundry sh",
"foundry:setup": "curl -L https://foundry.paradigm.xyz | bash && foundryup && git submodule update --init --recursive",
"foundry:size": "forge build --skip test --sizes",
"foundry:test": "FOUNDRY_PROFILE=dev forge test -vvv",
"foundry:test": "FOUNDRY_PROFILE=dev forge test -vvv --no-match-contract \"UpgradeDistributionCreatorTest\"",
"impersonate": "cast rpc anvil_impersonateAccount",
"impersonate:script": "FOUNDRY_PROFILE=dev forge script --skip test --fork-url fork --broadcast -vvvv --gas-price 0 --priority-gas-price 0 --unlocked --sender",
"impersonate:setBalance": "cast rpc anvil_setBalance 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776 1000000000000000000 && cast rpc anvil_setBalance 0x15775b23340C0f50E0428D674478B0e9D3D0a759 1000000000000000000 && cast rpc anvil_setBalance 0x19c41f6607b2c0e80e84baadaf886b17565f278e 1000000000000000000 && cast rpc anvil_setBalance 0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701 1000000000000000000",
Expand All @@ -30,15 +30,16 @@
"url": "https://github.com/AngleProtocol/merkl-contracts/issues"
},
"devDependencies": {
"@angleprotocol/sdk": "2.34.5",
"@openzeppelin/contracts": "^4.8.1",
"@openzeppelin/contracts-upgradeable": "4.8.1",
"@angleprotocol/sdk": "2.34.7",
"@openzeppelin/contracts": "^4.9.0",
"@openzeppelin/contracts-upgradeable": "4.9.0",
"prettier": "^2.0.0",
"prettier-plugin-solidity": "^1.1.3",
"solhint": "^3.5.1",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.8.2",
"forge-std": "github:foundry-rs/forge-std#v1.9.4"
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
"utils": "github:AngleProtocol/utils"
},
"dependencies": {}
}
}
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@openzeppelin/=node_modules/@openzeppelin/
forge-std/=node_modules/forge-std/src
oz/=node_modules/@openzeppelin/contracts/
utils/=lib/utils
@utils/=node_modules/utils/src
2 changes: 1 addition & 1 deletion scripts/Disputer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ pragma solidity ^0.8.17;

import { console } from "forge-std/console.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { JsonReader } from "@utils/JsonReader.sol";

import { BaseScript } from "./utils/Base.s.sol";
import { JsonReader } from "./utils/JsonReader.sol";
import { Disputer } from "../contracts/Disputer.sol";
import { Distributor } from "../contracts/Distributor.sol";

Expand Down
Loading

0 comments on commit f72f358

Please sign in to comment.