From 6a3ca3171802677517005831fc85b618a655acf1 Mon Sep 17 00:00:00 2001 From: andreivladbrg Date: Thu, 7 Dec 2023 16:43:43 +0200 Subject: [PATCH] feat: add a deployment bash script for multiple chains feat: add deploy protocol 2 scripts build: remove etherscan api keys from foundry.toml build: improve .env.example chore: git ignore deployments --- .env.example | 47 +++-- .gitignore | 1 + foundry.toml | 13 +- script/DeployDeterministicProtocol2.s.sol | 47 +++++ script/DeployProtocol2.s.sol | 40 +++++ shell/deploy-multi-chains.sh | 198 ++++++++++++++++++++++ 6 files changed, 324 insertions(+), 22 deletions(-) create mode 100644 script/DeployDeterministicProtocol2.s.sol create mode 100644 script/DeployProtocol2.s.sol create mode 100755 shell/deploy-multi-chains.sh diff --git a/.env.example b/.env.example index fe68b0c0..26b14380 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,39 @@ -export API_KEY_ARBISCAN="YOUR_API_KEY_ARBISCAN" -export API_KEY_BSCSCAN="YOUR_API_KEY_BSCSCAN" -export API_KEY_ETHERSCAN="YOUR_API_KEY_ETHERSCAN" -export API_KEY_GNOSISSCAN="YOUR_API_KEY_GNOSISSCAN" -export API_KEY_INFURA="YOUR_API_KEY_INFURA" -export API_KEY_OPTIMISTIC_ETHERSCAN="YOUR_API_KEY_OPTIMISTIC_ETHERSCAN" -export API_KEY_POLYGONSCAN="YOUR_API_KEY_POLYGONSCAN" -export API_KEY_SNOWTRACE="YOUR_API_KEY_SNOWTRACE" +# Run `cp .env.example .env` command to create your .env file + +# General +export API_KEY_INFURA="YOUR_INFURA_KEY" export FOUNDRY_PROFILE="lite" export MNEMONIC="YOUR_MNEMONIC" -export RPC_URL_GOERLI="YOUR_RPC_URL_GOERLI" -export RPC_URL_MAINNET="YOUR_RPC_URL_MAINNET" + +# RPC URLs +export ARBITRUM_RPC_URL="YOUR_RPC_URL" +export AVALANCHE_RPC_URL="YOUR_RPC_URL" +export BASE_RPC_URL="YOUR_RPC_URL" +export BINANCE_RPC_URL="YOUR_RPC_URL" +export GNOSIS_RPC_URL="YOUR_RPC_URL" +export MAINNET_RPC_URL="YOUR_RPC_URL" +export OPTIMISM_RPC_URL="YOUR_RPC_URL" +export POLYGON_RPC_URL="YOUR_RPC_URL" +export SCROLL_RPC_URL="YOUR_RPC_URL" + +# Etherscan API keys +export ARBISCAN_API_KEY="YOUR_API_KEY" +export BASESCAN_API_KEY="YOUR_API_KEY" +export BSCSCAN_API_KEY="YOUR_API_KEY" +export ETHERSCAN_API_KEY="YOUR_API_KEY" +export GNOSISSCAN_API_KEY="YOUR_API_KEY" +export OPTIMISTIC_API_KEY="YOUR_API_KEY" +export POLYGONSCAN_API_KEY="YOUR_API_KEY" +export SCROLL_API_KEY="YOUR_API_KEY" +export SNOWTRACE_API_KEY="YOUR_API_KEY" + +# Script variables +export ARBITRUM_ADMIN="YOUR_ADMIN_ADDRESS" +export AVALANCHE_ADMIN="YOUR_ADMIN_ADDRESS" +export BASE_ADMIN="YOUR_ADMIN_ADDRESS" +export BSC_ADMIN="YOUR_ADMIN_ADDRESS" +export GNOSIS_ADMIN="YOUR_ADMIN_ADDRESS" +export MAINNET_ADMIN="YOUR_ADMIN_ADDRESS" +export OPTIMISM_ADMIN="YOUR_ADMIN_ADDRESS" +export POLYGON_ADMIN="YOUR_ADMIN_ADDRESS" +export SCROLL_ADMIN="YOUR_ADMIN_ADDRESS" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 30006812..c61a82ea 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ artifacts broadcast cache coverage +deployments docs node_modules out-optimized diff --git a/foundry.toml b/foundry.toml index 9aebcb61..652b41eb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -41,17 +41,6 @@ out = "docs" repository = "https://github.com/sablier-labs/v2-periphery" -[etherscan] - arbitrum_one = { key = "${API_KEY_ARBISCAN}" } - avalanche = { key = "${API_KEY_SNOWTRACE" } - bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" } - gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" } - goerli = { key = "${API_KEY_ETHERSCAN}" } - mainnet = { key = "${API_KEY_ETHERSCAN}" } - optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" } - polygon = { key = "${API_KEY_POLYGONSCAN}" } - sepolia = { key = "${API_KEY_ETHERSCAN}" } - [fmt] bracket_spacing = true int_types = "long" @@ -69,7 +58,7 @@ gnosis_chain = "https://rpc.gnosischain.com" goerli = "${RPC_URL_GOERLI}" localhost = "http://localhost:8545" - mainnet = "${RPC_URL_MAINNET}" + mainnet = "${MAINNET_RPC_URL}" optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}" polygon = "https://polygon-mainnet.infura.io/v3/${API_KEY_INFURA}" sepolia = "RPC_URL_SEPOLIA" diff --git a/script/DeployDeterministicProtocol2.s.sol b/script/DeployDeterministicProtocol2.s.sol new file mode 100644 index 00000000..845b852b --- /dev/null +++ b/script/DeployDeterministicProtocol2.s.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19 <0.9.0; + +import { SablierV2Comptroller } from "@sablier/v2-core/src/SablierV2Comptroller.sol"; +import { SablierV2LockupDynamic } from "@sablier/v2-core/src/SablierV2LockupDynamic.sol"; +import { SablierV2LockupLinear } from "@sablier/v2-core/src/SablierV2LockupLinear.sol"; +import { SablierV2NFTDescriptor } from "@sablier/v2-core/src/SablierV2NFTDescriptor.sol"; + +import { BaseScript } from "./Base.s.sol"; + +import { SablierV2Batch } from "../src/SablierV2Batch.sol"; +import { SablierV2MerkleStreamerFactory } from "../src/SablierV2MerkleStreamerFactory.sol"; + +/// @notice Deploys the Sablier V2 Protocol deterministically expect for comptroller. +contract DeployDeterministicPeriphery is BaseScript { + /// @dev The presence of the salt instructs Forge to deploy the contract via a deterministic CREATE2 factory. + /// https://github.com/Arachnid/deterministic-deployment-proxy + function run( + string memory create2Salt, + address initialAdmin, + SablierV2Comptroller comptroller + ) + public + virtual + broadcast + returns ( + SablierV2LockupDynamic lockupDynamic, + SablierV2LockupLinear lockupLinear, + SablierV2NFTDescriptor nftDescriptor, + SablierV2Batch batch, + SablierV2MerkleStreamerFactory merkleStreamerFactory + ) + { + bytes32 salt = bytes32(abi.encodePacked(create2Salt)); + + nftDescriptor = new SablierV2NFTDescriptor{ salt: salt }(); + lockupLinear = new SablierV2LockupLinear{ salt: salt }(initialAdmin, comptroller, nftDescriptor); + + uint256 maxSegmentCount = 300; + + lockupDynamic = + new SablierV2LockupDynamic{ salt: salt }(initialAdmin, comptroller, nftDescriptor, maxSegmentCount); + + batch = new SablierV2Batch{ salt: salt }(); + merkleStreamerFactory = new SablierV2MerkleStreamerFactory{ salt: salt }(); + } +} diff --git a/script/DeployProtocol2.s.sol b/script/DeployProtocol2.s.sol new file mode 100644 index 00000000..965399a6 --- /dev/null +++ b/script/DeployProtocol2.s.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19 <0.9.0; + +import { SablierV2Comptroller } from "@sablier/v2-core/src/SablierV2Comptroller.sol"; +import { SablierV2LockupDynamic } from "@sablier/v2-core/src/SablierV2LockupDynamic.sol"; +import { SablierV2LockupLinear } from "@sablier/v2-core/src/SablierV2LockupLinear.sol"; +import { SablierV2NFTDescriptor } from "@sablier/v2-core/src/SablierV2NFTDescriptor.sol"; +import { BaseScript } from "./Base.s.sol"; + +import { SablierV2MerkleStreamerFactory } from "../src/SablierV2MerkleStreamerFactory.sol"; +import { SablierV2Batch } from "../src/SablierV2Batch.sol"; + +/// @notice Deploys the Sablier V2 Protocol expect for comptroller. +contract DeployProtocol is BaseScript { + function run( + address initialAdmin, + SablierV2Comptroller comptroller + ) + public + virtual + broadcast + returns ( + SablierV2LockupDynamic lockupDynamic, + SablierV2LockupLinear lockupLinear, + SablierV2NFTDescriptor nftDescriptor, + SablierV2Batch batch, + SablierV2MerkleStreamerFactory merkleStreamerFactory + ) + { + uint256 maxSegmentCount = 300; + + // Deploy V2 Core. + nftDescriptor = new SablierV2NFTDescriptor(); + lockupDynamic = new SablierV2LockupDynamic(initialAdmin, comptroller, nftDescriptor, maxSegmentCount); + lockupLinear = new SablierV2LockupLinear(initialAdmin, comptroller, nftDescriptor); + + batch = new SablierV2Batch(); + merkleStreamerFactory = new SablierV2MerkleStreamerFactory(); + } +} diff --git a/shell/deploy-multi-chains.sh b/shell/deploy-multi-chains.sh new file mode 100755 index 00000000..379a73d8 --- /dev/null +++ b/shell/deploy-multi-chains.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +# Usage: ./shell/deploy-multi-chains.sh [options] [chain1 [chain2 ...]] +# Options: +# --deterministic Deploy using the deterministic script. +# --broadcast Broadcast the deployment and verify on Etherscan. +# Example: ./shell/deploy-multi-chains.sh # Default deploys only to Sepolia +# Example: ./shell/deploy-multi-chains.sh --broadcast arbitrum_one mainnet +# Example: ./shell/deploy-multi-chains.sh --deterministic --broadcast mainnet + +# Make sure you set-up your .env file first. See .env.example. + +# Pre-requisites: +# - foundry (https://getfoundry.sh) + +# Strict mode: https://gist.github.com/vncsna/64825d5609c146e80de8b1fd623011ca +set -euo pipefail + +# Create deployments directory +deployments=./deployments +rm -rf $deployments +mkdir $deployments + +# Declare chain IDs +ARBITRUM_CHAIN_ID="42161" +AVALANCHE_CHAIN_ID="43114" +BASE_CHAIN_ID="8453" +BSC_CHAIN_ID="56" +GOERLI_CHAIN_ID="5" +GNOSIS_CHAIN_ID="100" +MAINNET_CHAIN_ID="1" +OPTIMISM_CHAIN_ID="10" +POLYGON_CHAIN_ID="137" +SCROLL_CHAIN_ID="534352" +SEPOLIA_CHAIN_ID="11155111" + +# from: https://docs.sablier.com/contracts/v2/deployments +ARBITRUM_COMPTROLLER="0x17Ec73692F0aDf7E7C554822FBEAACB4BE781762" +AVALANCHE_COMPTROLLER="0x66F5431B0765D984f82A4fc4551b2c9ccF7eAC9C" +BASE_COMPTROLLER="0x7Faaedd40B1385C118cA7432952D9DC6b5CbC49e" +BSC_COMPTROLLER="0x33511f69A784Fd958E6713aCaC7c9dCF1A5578E8" +MAINNET_COMPTROLLER="0xC3Be6BffAeab7B297c03383B4254aa3Af2b9a5BA" +GNOSIS_COMPTROLLER="0x73962c44c0fB4cC5e4545FB91732a5c5e87F55C2" +OPTIMISM_COMPTROLLER="0x1EECb6e6EaE6a1eD1CCB4323F3a146A7C5443A10" +POLYGON_COMPTROLLER="0x9761692EDf10F5F2A69f0150e2fd50dcecf05F2E" +SCROLL_COMPTROLLER="0x859708495E3B3c61Bbe19e6E3E1F41dE3A5C5C5b" +SEPOLIA_COMPTROLLER="0x2006d43E65e66C5FF20254836E63947FA8bAaD68" + +# Define chain configurations +declare -A chains +chains["arbitrum_one"]="$ARBITRUM_RPC_URL $ARBISCAN_API_KEY $ARBITRUM_CHAIN_ID $ARBITRUM_ADMIN $ARBITRUM_COMPTROLLER" +chains["avalanche"]="$AVALANCHE_RPC_URL $SNOWTRACE_API_KEY $AVALANCHE_CHAIN_ID $AVALANCHE_ADMIN $AVALANCHE_COMPTROLLER" +chains["base"]="$BASE_RPC_URL $BASESCAN_API_KEY $BASE_CHAIN_ID $BASE_ADMIN $BASE_ADMIN $BASE_COMPTROLLER" +chains["bnb_smart_chain"]="$BSC_RPC_URL $BSCSCAN_API_KEY $BSC_CHAIN_ID $BSC_ADMIN $BSC_COMPTROLLER" +chains["gnosis"]="$GNOSIS_RPC_URL $GNOSISSCAN_API_KEY $GNOSIS_CHAIN_ID $GNOSIS_ADMIN $GNOSIS_COMPTROLLER" +chains["mainnet"]="$MAINNET_RPC_URL $ETHERSCAN_API_KEY $MAINNET_CHAIN_ID $MAINNET_ADMIN $MAINNET_COMPTROLLER" +chains["optimism"]="$OPTIMISM_RPC_URL $OPTIMISTIC_API_KEY $OPTIMISM_CHAIN_ID $OPTIMISM_ADMIN $OPTIMISM_COMPTROLLER" +chains["polygon"]="$POLYGON_RPC_URL $POLYGONSCAN_API_KEY $POLYGON_CHAIN_ID $POLYGON_ADMIN $POLYGON_COMPTROLLER" +chains["scroll"]="$SCROLL_RPC_URL $SCROLL_API_KEY $SCROLL_CHAIN_ID $SCROLL_ADMIN $SCROLL_COMPTROLLER" +chains["sepolia"]="$SEPOLIA_RPC_URL $ETHERSCAN_API_KEY $SEPOLIA_CHAIN_ID $SEPOLIA_ADMIN $SEPOLIA_COMPTROLLER" + +# Flag for broadcast deployment +BROADCAST_DEPLOYMENT=false + +# Flag for deterministic deployment +DETERMINISTIC_DEPLOYMENT=false + +# Flag for gas price +WITH_GAS_PRICE=false +GAS_PRICE=0 + +# Requested chains +requested_chains=() + +# Check for arguments passed to the script +for ((i=1; i<=$#; i++)); do + arg=${!i} + + # Check for '--broadcast' flag in the arguments + if [[ $arg == "--broadcast" ]]; then + BROADCAST_DEPLOYMENT=true + fi + + # Check for '--broadcast' flag in the arguments + if [[ $arg == "--deterministic" ]]; then + DETERMINISTIC_DEPLOYMENT=true + fi + + # Check for '--with-gas-price' flag in the arguments + if [[ $arg == "--with-gas-price" ]]; then + WITH_GAS_PRICE=true + # Increment index to get the next argument, which should be the gas price + ((i++)) + GAS_PRICE=${!i} + if ! [[ $GAS_PRICE =~ ^[0-9]+$ ]]; then + echo "Error: Gas price must be a number." + exit 1 + fi + fi + + # Check for '--all' flag in the arguments + if [[ $arg == "--all" ]]; then + requested_chains=("${!chains[@]}") + fi + + # Check for passed chains + if [[ $arg != "--all" && $arg != "--deterministic" && $arg != "--broadcast" && $arg != "--with-gas-price" ]]; then + requested_chains+=("$arg") + fi +done + +# Set the default chain to Sepolia if no chains are requested +if [ ${#requested_chains[@]} -eq 0 ]; then + requested_chains=("sepolia") +fi + +# Compile the contracts +echo "Compiling the contracts..." +FOUNDRY_PROFILE=optimized forge build + +# Deploy to requested chains +for chain in "${requested_chains[@]}"; do + # Check if the requested chain is defined + if [[ ! -v "chains[$chain]" ]]; then + echo "Chain configuration for '$chain' not found." + continue + fi + + # Split the configuration into RPC, API key and the Chain ID + IFS=' ' read -r rpc_url api_key chain_id admin comptroller<<< "${chains[$chain]}" + + # Declare a deployment command + deployment_command=""; + + # Choose the script based on the flag + if [[ $DETERMINISTIC_DEPLOYMENT == true ]]; then + echo "Deploying deterministic contracts to $chain..." + # Construct the command + deployment_command="forge script script/DeployDeterministicProtocol2.s.sol \ + --rpc-url $rpc_url \ + --sig run(string,address,address) \ + \"ChainID $chain_id, Version 1.1.0\" \ + $admin \ + $comptroller \ + -vvv" + else + echo "Deploying contracts to $chain..." + # Construct the command + deployment_command="forge script script/DeployProtocol2.s.sol \ + --rpc-url $rpc_url \ + --sig run(address,address) \ + $admin \ + $comptroller \ + -vvv" + fi + + # Append additional options if broadcast is enabled + if [[ $BROADCAST_DEPLOYMENT == true ]]; then + echo "This deployment is broadcasted on $chain" + deployment_command+=" --broadcast --verify --etherscan-api-key \"$api_key\"" + else + echo "This deployment is simulated on $chain" + fi + + # Append additional options if gas price is enabled + if [[ $WITH_GAS_PRICE == true ]]; then + gas_price_in_gwei=$(echo "scale=2; $GAS_PRICE / 1000000000" | bc) + echo "This deployment is using gas price of $gas_price_in_gwei gwei" + deployment_command+=" --with-gas-price $GAS_PRICE" + fi + + # Run the deployment command + output=$(FOUNDRY_PROFILE=optimized $deployment_command) + + # Create a file for the chain + chain_file="$deployments/$chain.txt" + touch "$chain_file" + + # Extract and save contract addresses + nftDescriptor_address=$(echo "$output" | awk '/nftDescriptor: contract/{print $NF}') + lockupLinear_address=$(echo "$output" | awk '/lockupLinear: contract/{print $NF}') + lockupDynamic_address=$(echo "$output" | awk '/lockupDynamic: contract/{print $NF}') + batch_address=$(echo "$output" | awk '/batch: contract/{print $NF}') + merkleStreamerFactory_address=$(echo "$output" | awk '/merkleStreamerFactory: contract/{print $NF}') + + # Save to the chain file + { + echo "NFTDescriptor = $nftDescriptor_address" + echo "LockupLinear = $lockupLinear_address" + echo "LockupDynamic = $lockupDynamic_address" + echo "SablierV2Batch = $batch_address" + echo "SablierV2MerkleStreamerFactory = $merkleStreamerFactory_address" + } >> "$chain_file" + + echo "Deployment for $chain done. Addresses saved in $chain_file" +done + +echo "All deployments completed." \ No newline at end of file