diff --git a/.env.example b/.env.example index 070a16583..d4aaa32fc 100644 --- a/.env.example +++ b/.env.example @@ -9,12 +9,13 @@ export MNEMONIC="YOUR_MNEMONIC" 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 BSC_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" +export SEPOLIA_RPC_URL="YOUR_RPC_URL" # Etherscan API keys export ARBISCAN_API_KEY="YOUR_API_KEY" @@ -37,4 +38,7 @@ export MAINNET_ADMIN="YOUR_ADMIN_ADDRESS" export OPTIMISM_ADMIN="YOUR_ADMIN_ADDRESS" export POLYGON_ADMIN="YOUR_ADMIN_ADDRESS" export SCROLL_ADMIN="YOUR_ADMIN_ADDRESS" +export SEPOLIA_ADMIN="YOUR_ADMIN_ADDRESS" + +# The maximum number of segments allowed in a stream export MAX_SEGMENTS_COUNT="THE_MAX_SEGMENT_COUNT" diff --git a/.github/workflows/ci-deep.yml b/.github/workflows/ci-deep.yml index e9be66280..8bd651492 100644 --- a/.github/workflows/ci-deep.yml +++ b/.github/workflows/ci-deep.yml @@ -3,7 +3,7 @@ name: "CI Deep" env: API_KEY_ETHERSCAN: ${{ secrets.API_KEY_ETHERSCAN }} API_KEY_INFURA: ${{ secrets.API_KEY_INFURA }} - RPC_URL_MAINNET: ${{ secrets.RPC_URL_MAINNET }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} on: schedule: @@ -93,7 +93,7 @@ jobs: run: "FOUNDRY_PROFILE=optimized forge build" - name: "Build the test contracts" - run: "FOUNDRY_PROFILE=test-optimized forge build" + run: "FOUNDRY_PROFILE=test-optimized forge build" - name: "Cache the build so that it can be re-used by the other jobs" uses: "actions/cache/save@v3" @@ -103,7 +103,7 @@ jobs: cache out out-optimized - + - name: "Add build summary" run: | echo "## Build result" >> $GITHUB_STEP_SUMMARY @@ -133,7 +133,7 @@ jobs: node-version: "lts/*" - name: "Install the Node.js dependencies" - run: "pnpm install" + run: "pnpm install" - name: "Restore the cached build" uses: "actions/cache/restore@v3" @@ -188,7 +188,7 @@ jobs: cache out out-optimized - + - name: "Run the integration tests against the optimized build" run: "FOUNDRY_PROFILE=test-optimized forge test --match-path \"test/integration/**/*.sol\"" @@ -222,8 +222,8 @@ jobs: node-version: "lts/*" - name: "Install the Node.js dependencies" - run: "pnpm install" - + run: "pnpm install" + - name: "Restore the cached build" uses: "actions/cache/restore@v3" @@ -234,7 +234,7 @@ jobs: cache out out-optimized - + - name: "Run the invariant tests against the optimized build" run: "FOUNDRY_PROFILE=test-optimized forge test --match-path \"test/invariant/**/*.sol\"" @@ -285,4 +285,4 @@ jobs: - name: "Add test summary" run: | echo "## Fork tests result" >> $GITHUB_STEP_SUMMARY - echo "✅ Passed" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d380eb196..4320bcc72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ concurrency: env: API_KEY_ETHERSCAN: ${{ secrets.API_KEY_ETHERSCAN }} API_KEY_INFURA: ${{ secrets.API_KEY_INFURA }} - RPC_URL_MAINNET: ${{ secrets.RPC_URL_MAINNET }} + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} on: workflow_dispatch: @@ -76,7 +76,7 @@ jobs: - name: "Generate and prepare the contract artifacts" run: "./shell/prepare-artifacts.sh" - + - name: "Build the test contracts" run: "FOUNDRY_PROFILE=test-optimized forge build" @@ -91,7 +91,7 @@ jobs: - name: "Install the Node.js dependencies" run: "pnpm install" - + - name: "Store the contract artifacts in CI" uses: "actions/upload-artifact@v3" with: @@ -126,8 +126,8 @@ jobs: node-version: "lts/*" - name: "Install the Node.js dependencies" - run: "pnpm install" - + run: "pnpm install" + - name: "Restore the cached build" uses: "actions/cache/restore@v3" with: @@ -255,7 +255,7 @@ jobs: - name: "Install the Node.js dependencies" run: "pnpm install" - + - name: "Restore the cached build" uses: "actions/cache/restore@v3" with: @@ -265,7 +265,7 @@ jobs: cache out out-optimized - + - name: "Run the invariant tests against the optimized build" run: "FOUNDRY_PROFILE=test-optimized forge test --match-path \"test/invariant/**/*.sol\"" @@ -309,7 +309,7 @@ jobs: cache out out-optimized - + - name: "Generate fuzz seed that changes weekly to avoid burning through RPC allowance" run: | echo "FOUNDRY_FUZZ_SEED=$(echo $(($EPOCHSECONDS / 604800)))" >> $GITHUB_ENV diff --git a/script/DeployCore3.s.sol b/script/DeployCore3.s.sol new file mode 100644 index 000000000..5d353c3de --- /dev/null +++ b/script/DeployCore3.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19 <0.9.0; + +import { ISablierV2Comptroller } from "../src/interfaces/ISablierV2Comptroller.sol"; +import { SablierV2NFTDescriptor } from "../src/SablierV2NFTDescriptor.sol"; +import { SablierV2LockupDynamic } from "../src/SablierV2LockupDynamic.sol"; +import { SablierV2LockupLinear } from "../src/SablierV2LockupLinear.sol"; + +import { BaseScript } from "./Base.s.sol"; + +/// @notice Deploys all V2 Core contract in the following order: +/// +/// 1. {SablierV2NFTDescriptor} +/// 2. {SablierV2LockupDynamic} +/// 3. {SablierV2LockupLinear} +contract DeployCore3 is BaseScript { + function run( + address initialAdmin, + ISablierV2Comptroller comptroller, + uint256 maxSegmentCount + ) + public + virtual + broadcast + returns ( + SablierV2NFTDescriptor nftDescriptor, + SablierV2LockupDynamic lockupDynamic, + SablierV2LockupLinear lockupLinear + ) + { + nftDescriptor = new SablierV2NFTDescriptor(); + lockupDynamic = new SablierV2LockupDynamic(initialAdmin, comptroller, nftDescriptor, maxSegmentCount); + lockupLinear = new SablierV2LockupLinear(initialAdmin, comptroller, nftDescriptor); + } +} diff --git a/script/DeployDeterministicCore3.s.sol b/script/DeployDeterministicCore3.s.sol new file mode 100644 index 000000000..3f8ec4c03 --- /dev/null +++ b/script/DeployDeterministicCore3.s.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.19 <0.9.0; + +import { ISablierV2Comptroller } from "../src/interfaces/ISablierV2Comptroller.sol"; +import { SablierV2NFTDescriptor } from "../src/SablierV2NFTDescriptor.sol"; +import { SablierV2LockupDynamic } from "../src/SablierV2LockupDynamic.sol"; +import { SablierV2LockupLinear } from "../src/SablierV2LockupLinear.sol"; + +import { BaseScript } from "./Base.s.sol"; + +/// @notice Deploys all V2 Core contracts at deterministic addresses across chains, in the following order: +/// +/// 1. {SablierV2Comptroller} +/// 2. {SablierV2LockupDynamic} +/// 3. {SablierV2LockupLinear} +/// +/// @dev Reverts if any contract has already been deployed. +contract DeployDeterministicCore3 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, + ISablierV2Comptroller comptroller, + uint256 maxSegmentCount + ) + public + virtual + broadcast + returns ( + SablierV2NFTDescriptor nftDescriptor, + SablierV2LockupDynamic lockupDynamic, + SablierV2LockupLinear lockupLinear + ) + { + bytes32 salt = bytes32(abi.encodePacked(create2Salt)); + nftDescriptor = new SablierV2NFTDescriptor{ salt: salt }(); + // forgefmt: disable-next-line + lockupDynamic = + new SablierV2LockupDynamic{ salt: salt }(initialAdmin, comptroller, nftDescriptor, maxSegmentCount); + lockupLinear = new SablierV2LockupLinear{ salt: salt }(initialAdmin, comptroller, nftDescriptor); + } +} diff --git a/shell/deploy-multi-chains.sh b/shell/deploy-multi-chains.sh index 7b2a60aab..f050ead27 100755 --- a/shell/deploy-multi-chains.sh +++ b/shell/deploy-multi-chains.sh @@ -1,10 +1,11 @@ - #!/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. +# --with-gas-price Specify gas price for transaction. +# --all Deploy on all chains. # 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 @@ -13,10 +14,21 @@ # Pre-requisites: # - foundry (https://getfoundry.sh) +# - bash version >=4.0.0 # Strict mode: https://gist.github.com/vncsna/64825d5609c146e80de8b1fd623011ca set -euo pipefail +# color codes +EC='\033[0;31m' # Error Color +SC='\033[0;32m' # Success Color +WC='\033[0;33m' # Warn Color +IC='\033[0;36m' # Info Color +NC='\033[0m' # No Color + +# Unicode characters for tick +TICK="\xE2\x9C\x94" + # Create deployments directory deployments=./deployments rm -rf $deployments @@ -39,7 +51,6 @@ 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" @@ -47,20 +58,34 @@ POLYGON_CHAIN_ID="137" SCROLL_CHAIN_ID="534352" SEPOLIA_CHAIN_ID="11155111" -echo $BASH_VERSION +# Source the .env file to load the variables +if [ -f .env ]; then + source .env +else + echo -e "${EC}Error: .env file not found${NC}" + exit 1 +fi + +# Check: required Bash >=4.0.0 for associative arrays +if ((BASH_VERSINFO[0] < 4)); then + echo -e "${EC}Error:\nThis script requires Bash version 4.0.0 or higher. + \nYou are currently using Bash version ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]}. + \nPlease upgrade your Bash version and try again.${NC}" + exit 1 +fi # Define chain configurations declare -A chains -chains["arbitrum_one"]="$ARBITRUM_RPC_URL $ARBISCAN_API_KEY $ARBITRUM_CHAIN_ID $ARBITRUM_ADMIN" -chains["avalanche"]="$AVALANCHE_RPC_URL $SNOWTRACE_API_KEY $AVALANCHE_CHAIN_ID $AVALANCHE_ADMIN" -chains["base"]="$BASE_RPC_URL $BASESCAN_API_KEY $BASE_CHAIN_ID $BASE_ADMIN" -chains["bnb_smart_chain"]="$BSC_RPC_URL $BSCSCAN_API_KEY $BSC_CHAIN_ID $BSC_ADMIN" -chains["gnosis"]="$GNOSIS_RPC_URL $GNOSISSCAN_API_KEY $GNOSIS_CHAIN_ID $GNOSIS_ADMIN" -chains["mainnet"]="$MAINNET_RPC_URL $ETHERSCAN_API_KEY $MAINNET_CHAIN_ID $MAINNET_ADMIN" -chains["optimism"]="$OPTIMISM_RPC_URL $OPTIMISTIC_API_KEY $OPTIMISM_CHAIN_ID $OPTIMISM_ADMIN" -chains["polygon"]="$POLYGON_RPC_URL $POLYGONSCAN_API_KEY $POLYGON_CHAIN_ID $POLYGON_ADMIN" -chains["scroll"]="$SCROLL_RPC_URL $SCROLL_API_KEY $SCROLL_CHAIN_ID $SCROLL_ADMIN" -chains["sepolia"]="$SEPOLIA_RPC_URL $ETHERSCAN_API_KEY $SEPOLIA_CHAIN_ID $SEPOLIA_ADMIN" +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_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 @@ -72,6 +97,9 @@ DETERMINISTIC_DEPLOYMENT=false WITH_GAS_PRICE=false GAS_PRICE=0 +# Flag for all chains +ON_ALL_CHAINS=false + # Requested chains requested_chains=() @@ -87,7 +115,7 @@ for ((i=1; i<=$#; i++)); do # Check for '--broadcast' flag in the arguments if [[ $arg == "--deterministic" ]]; then DETERMINISTIC_DEPLOYMENT=true - fi + fi # Check for '--with-gas-price' flag in the arguments if [[ $arg == "--with-gas-price" ]]; then @@ -96,18 +124,19 @@ for ((i=1; i<=$#; i++)); do ((i++)) GAS_PRICE=${!i} if ! [[ $GAS_PRICE =~ ^[0-9]+$ ]]; then - echo "Error: Gas price must be a number." + echo -e "${EC}Error: Invalid value for --with-gas-price, must be number${NC}" exit 1 fi fi # Check for '--all' flag in the arguments if [[ $arg == "--all" ]]; then + ON_ALL_CHAINS=true requested_chains=("${!chains[@]}") fi # Check for passed chains - if [[ $arg != "--all" && $arg != "--deterministic" && $arg != "--broadcast" && $arg != "--with-gas-price" ]]; then + if [[ $arg != "--all" && $arg != "--deterministic" && $arg != "--broadcast" && $arg != "--with-gas-price" && $ON_ALL_CHAINS == false ]]; then requested_chains+=("$arg") fi done @@ -125,48 +154,52 @@ FOUNDRY_PROFILE=optimized forge build 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." + echo -e "\n${WC}Warning: Chain configuration for '$chain' not found.${NC}" continue fi # Split the configuration into RPC, API key, Chain ID and the admin address - IFS=' ' read -r rpc_url api_key chain_id admin <<< "${chains[$chain]}" + 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..." + echo -e "\n${IC}Deploying deterministic contracts to $chain...${NC}" # Construct the command - deployment_command="forge script script/DeployDeterministicCore.s.sol \ + deployment_command="forge script script/DeployDeterministicCore3.s.sol \ --rpc-url $rpc_url \ - --sig run(string,address,uint256) \ - \"ChainID $chain_id, Version 1.1.0\" \ + --sig run(string,address,address,uint256) \ + \'ChainID $chain_id, Version 1.1.0\' \ $admin \ + $comptroller \ $MAX_SEGMENTS_COUNT \ - -vv" + -vvv" else - echo "Deploying contracts to $chain..." + echo -e "\n${IC}Deploying contracts to $chain...${NC}" # Construct the command - deployment_command="forge script script/DeployCore.s.sol \ + deployment_command="forge script script/DeployCore3.s.sol \ --rpc-url $rpc_url \ - --sig run(address,uint256) \ + --sig run(address,address,uint256) \ $admin \ + $comptroller \ $MAX_SEGMENTS_COUNT \ - -vv" + -vvv" fi # Append additional options if broadcast is enabled - if [[ $BROADCAST_DEPLOYMENT == true ]]; then - echo "This deployment is broadcasted on $chain" + if [[ $BROADCAST_DEPLOYMENT == true ]]; then + echo -e "${SC}+${NC} Broadcasting on $chain" deployment_command+=" --broadcast --verify --etherscan-api-key \"$api_key\"" + else + echo -e "${SC}+${NC} Simulating 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" + echo -e "${SC}+${NC} Using gas price of $gas_price_in_gwei gwei" deployment_command+=" --with-gas-price $GAS_PRICE" fi @@ -178,20 +211,18 @@ for chain in "${requested_chains[@]}"; do touch "$chain_file" # Extract and save contract addresses - comptroller_address=$(echo "$output" | awk '/comptroller: contract/{print $NF}') lockupDynamic_address=$(echo "$output" | awk '/lockupDynamic: contract/{print $NF}') lockupLinear_address=$(echo "$output" | awk '/lockupLinear: contract/{print $NF}') nftDescriptor_address=$(echo "$output" | awk '/nftDescriptor: contract/{print $NF}') # Save to the chain file { - echo "SablierV2Comptroller = $comptroller_address" echo "SablierV2LockupDynamic = $lockupDynamic_address" echo "SablierV2LockupLinear = $lockupLinear_address" echo "SablierV2NFTDescriptor = $nftDescriptor_address" } >> "$chain_file" - echo "Deployment for $chain done. Addresses saved in $chain_file" + echo -e "${SC}$TICK Deployed on $chain. Addresses saved in $chain_file${NC}" done -echo "All deployments completed." +echo -e "\nAll deployments completed."