From f523526d4c94a2091046209526f75a857ad2325b Mon Sep 17 00:00:00 2001 From: John Terzis Date: Fri, 6 Sep 2024 15:44:45 -0700 Subject: [PATCH 01/30] update bash script --- .../bytecode-diff/scripts/upgrade-facets.sh | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100755 scripts/bytecode-diff/scripts/upgrade-facets.sh diff --git a/scripts/bytecode-diff/scripts/upgrade-facets.sh b/scripts/bytecode-diff/scripts/upgrade-facets.sh new file mode 100755 index 000000000..64be1b1b2 --- /dev/null +++ b/scripts/bytecode-diff/scripts/upgrade-facets.sh @@ -0,0 +1,156 @@ +#!/bin/bash +set -ueo pipefail +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" +cd .. + +# Function to find the most recent file +find_most_recent_file() { + local dir="$1" + find "$dir" -name "facet_diff_*.yaml" | sort -r | head -n 1 +} + +# Check if the correct number of arguments is provided +if [ $# -lt 1 ] || [ $# -gt 3 ]; then + echo "Usage: $0 [diff_directory] [file_name]" + echo " : Must be either 'gamma' or 'omega'" + exit 1 +fi + +# Set the network and validate it +network="$1" +if [ "$network" != "gamma" ] && [ "$network" != "omega" ]; then + echo "Error: Network must be either 'gamma' or 'omega'" + exit 1 +fi + +# Set default values and parse additional arguments +diff_dir="deployed-diffs" +file_name="" + +if [ $# -eq 2 ]; then + diff_dir="$2" +elif [ $# -eq 3 ]; then + diff_dir="$2" + file_name="$3" +fi + +# If file_name is not provided, find the most recent file +if [ -z "$file_name" ]; then + most_recent_file=$(find_most_recent_file "$diff_dir") + if [ -n "$most_recent_file" ]; then + file_name=$(basename "$most_recent_file") + echo "Using most recent file: $file_name" + else + echo "No matching files found in $diff_dir" + exit 1 + fi +fi + +# Function to process a single YAML file +process_file() { + local file="$1" + echo "Processing file: $file" + + # Extract originContractNames into an array, strip "Facet" suffix, and remove duplicates + contract_names=($(yq e '.diamonds[].facets[].originContractName' "$file" | sed 's/Facet$//' | sort -u)) + + # Determine which make command to use + if [[ "$network" == "omega" ]]; then + chain_id=8453 + context="omega" + make_command="make deploy-base" + elif [[ "$network" == "gamma" ]]; then + chain_id=84532 + context="gamma" + make_command="make deploy-base-sepolia" + else + echo "Error: Unknown file type $file. Cannot determine chain ID and context." + exit 1 + fi + + # Loop through each contract name and call the appropriate make command + if [ ${#contract_names[@]} -eq 0 ]; then + echo "No contracts to deploy." + exit 0 + else + current_dir=$(pwd) + cd ../../contracts + for contract in "${contract_names[@]}"; do + deploy_file=$(find ./scripts -name "Deploy${contract}.s.sol" -o -name "Deploy${contract}Facet.s.sol" | head -n1) + + if [ -n "$deploy_file" ]; then + deploy_contract=$(basename "$deploy_file" .s.sol) + echo "Deploying contract: $contract using $deploy_contract to chain $chain_id with context $context" + OVERRIDE_DEPLOYMENTS=1 $make_command context="$context" type=facets contract="$deploy_contract" + + if [ $? -ne 0 ]; then + echo "Error deploying $contract" + fi + else + echo "Error: Deploy file not found for $contract. Skipping." + fi + done + cd "$current_dir" + fi + + # Call process_deployments after processing the file + process_deployments "$chain_id" "$file" "${contract_names[@]}" +} + +# Function to process deployments and create a new YAML file +process_deployments() { + local chain_id="$1" + local input_file="$2" + shift 2 + local contract_names=("$@") + + # Append a new deployments section to the input file + echo -e "\ndeployments:" >> "$input_file" + + for contract in "${contract_names[@]}"; do + local json_file="../../broadcast/Deploy${contract}.s.sol/${chain_id}/run-latest.json" + if [[ -f "$json_file" ]]; then + local contract_name=$(jq -r '.transactions[0].contractName' "$json_file") + local contract_address=$(jq -r '.transactions[0].contractAddress' "$json_file") + local tx_hash=$(jq -r '.transactions[0].hash' "$json_file") + local deployment_date=$(date -r "$json_file" -u +"%Y-%m-%d %H:%M") + + # Determine the baseScanLink based on the chain_id + if [ "$chain_id" == "84532" ]; then + local base_scan_link="https://sepolia.basescan.org/tx/$tx_hash" + elif [ "$chain_id" == "8453" ]; then + local base_scan_link="https://basescan.org/tx/$tx_hash" + else + local base_scan_link="" + fi + + # Append deployment information for each contract + echo " $contract_name:" >> "$input_file" + echo " address: $contract_address" >> "$input_file" + echo " transactionHash: $tx_hash" >> "$input_file" + echo " deploymentDate: $deployment_date" >> "$input_file" + echo " bytecodeHash: " >> "$input_file" + if [ -n "$base_scan_link" ]; then + echo " baseScanLink: $base_scan_link" >> "$input_file" + fi + fi + done + + echo "Deployment information appended to $input_file" +} + +# Main script +if [ -n "$file_name" ]; then + if [[ -f "$diff_dir/$file_name" ]]; then + process_file "$diff_dir/$file_name" + else + echo "Error: Specified file $diff_dir/$file_name not found." + exit 1 + fi +else + for file in "$diff_dir"/diff_*.yaml; do + if [[ -f "$file" ]]; then + process_file "$file" + fi + done +fi \ No newline at end of file From e708d701317bbb79ea3d9f477606af9209642984 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Fri, 6 Sep 2024 15:45:16 -0700 Subject: [PATCH 02/30] add new cmd to strictly calc bytecode hashes and split from root cmd --- scripts/bytecode-diff/README.md | 22 ++ scripts/bytecode-diff/cmd/root.go | 364 ++++++++++++++++++++++++ scripts/bytecode-diff/cmd/run_util.go | 121 ++++++++ scripts/bytecode-diff/go.mod | 1 + scripts/bytecode-diff/main.go | 356 +---------------------- scripts/bytecode-diff/utils/ethereum.go | 29 +- 6 files changed, 529 insertions(+), 364 deletions(-) create mode 100644 scripts/bytecode-diff/cmd/root.go create mode 100644 scripts/bytecode-diff/cmd/run_util.go diff --git a/scripts/bytecode-diff/README.md b/scripts/bytecode-diff/README.md index 245f1a888..0f18dd67c 100644 --- a/scripts/bytecode-diff/README.md +++ b/scripts/bytecode-diff/README.md @@ -83,6 +83,28 @@ diamonds: ``` +### Run keccak256 hash generation on deployed contracts + +```bash +GOWORK=off go run main.go add-hashes gamma deployed-diffs/facet_diff_090624_1.yaml + +# output to new yaml file suffixed with _hashed.yaml including bytecodeHash for each contract in deployments section +➜ bytecode-diff git:(jt/net-62-upgrade-script-2) ✗ yq e '.deployments' deployed-diffs/facet_diff_090624_1_hashed.yaml +Architect: + address: 0xa18a3df4f63cdcae943d9c76730adf2812388de4 + baseScanLink: https://sepolia.basescan.org/tx/0x4280ef1300fe001e7d85e7495eba13fc99be53ee7a7060e753d466f8bebf1622 + bytecodeHash: 0x20d0a86e9ea31a39663285aacfe88705983520a4482a7bac5ada891c9adfe090 + deploymentDate: 2024-09-06 19:04 + transactionHash: 0x4280ef1300fe001e7d85e7495eba13fc99be53ee7a7060e753d466f8bebf1622 +Banning: + address: 0x4d88d1fbba6ce6bcdb4381549ee0b7c0d2b56919 + baseScanLink: https://sepolia.basescan.org/tx/0x4ccbaf9750bcd0971975e73a24b05f1c51d4703cf72a406356c79eb54de9c33c + bytecodeHash: 0xa2ce3e77ba060ff1d59ed384e1c6c5788f308ad8bbbef612eb3e5de4e1d79de8 + deploymentDate: 2024-09-06 19:05 + transactionHash: 0x4ccbaf9750bcd0971975e73a24b05f1c51d4703cf72a406356c79eb54de9c33c +... +``` + ### Flags ```bash diff --git a/scripts/bytecode-diff/cmd/root.go b/scripts/bytecode-diff/cmd/root.go new file mode 100644 index 000000000..cc4380db6 --- /dev/null +++ b/scripts/bytecode-diff/cmd/root.go @@ -0,0 +1,364 @@ +package cmd + +import ( + "bytecode-diff/utils" + "fmt" + "os" + "path/filepath" + + "github.com/joho/godotenv" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +var ( + supportedEnvironments = []string{"alpha", "gamma", "omega"} + baseRpcUrl string + facetSourcePath string + compiledFacetsPath string + sourceDiffDir string + sourceDiff bool + reportOutDir string + originEnvironment string + targetEnvironment string + deploymentsPath string + baseSepoliaRpcUrl string + logLevel string +) + +func init() { + log := zerolog.New(os.Stderr).With().Timestamp().Logger() + utils.SetLogger(log) + + rootCmd.PersistentFlags(). + StringVar(&logLevel, "log-level", "info", "Set the logging level (debug, info, warn, error)") + rootCmd.Flags().StringVarP(&baseRpcUrl, "base-rpc", "b", "", "Base RPC provider URL") + rootCmd.Flags().StringVarP(&baseSepoliaRpcUrl, "base-sepolia-rpc", "", "", "Base Sepolia RPC provider URL") + rootCmd.Flags().BoolVarP(&sourceDiff, "source-diff-only", "s", false, "Run source code diff") + rootCmd.Flags().StringVar(&sourceDiffDir, "source-diff-log", "source-diffs", "Path to diff log file") + rootCmd.Flags().StringVar(&compiledFacetsPath, "compiled-facets", "../../contracts/out", "Path to compiled facets") + rootCmd.Flags().StringVar(&facetSourcePath, "facets", "", "Path to facet source files") + rootCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output") + rootCmd.Flags().StringVar(&reportOutDir, "report-out-dir", "deployed-diffs", "Path to report output directory") + rootCmd.Flags(). + StringVar(&deploymentsPath, "deployments", "../../contracts/deployments", "Path to deployments directory") + + rootCmd.AddCommand(AddHashesCmd) +} + +func setLogLevel(level string) { + switch level { + case "debug": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "info": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "warn": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "error": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + default: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } +} + +func Execute() { + if err := godotenv.Load(); err != nil { + log.Warn().Msg("No .env file found") + } + + if err := rootCmd.Execute(); err != nil { + log.Error().Err(err).Msg("Error executing root command") + os.Exit(1) + } +} + +var rootCmd = &cobra.Command{ + Use: "bytecode-diff [origin_environment] [target_environment]", + Short: "A tool to retrieve and display contract bytecode diff for Base", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + setLogLevel(logLevel) + }, + Args: func(cmd *cobra.Command, args []string) error { + if sourceDiff { + if len(args) != 0 { + return fmt.Errorf("no positional arguments expected when --source-diff-only is set") + } + } else { + if len(args) < 2 { + return fmt.Errorf("at least two arguments required when --source-diff-only is not set, [origin_environment], [target_environment]") + } + } + return nil + }, + PreRun: func(cmd *cobra.Command, args []string) { + if sourceDiff { + envSourceDiffDir := os.Getenv("SOURCE_DIFF_DIR") + if envSourceDiffDir != "" { + sourceDiffDir = envSourceDiffDir + } + + if sourceDiffDir == "" { + sourceDiffDir = cmd.Flag("source-diff-log").Value.String() + } + + facetSourcePath = os.Getenv("FACET_SOURCE_PATH") + if facetSourcePath == "" { + facetSourcePath = cmd.Flag("facets").Value.String() + } + if facetSourcePath == "" { + log.Fatal(). + Msg("Facet source path is missing. Set it using --facets flag or FACET_SOURCE_PATH environment variable") + } + + compiledFacetsPath = os.Getenv("COMPILED_FACETS_PATH") + log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from environment") + if compiledFacetsPath == "" { + compiledFacetsPath = cmd.Flag("compiled-facets").Value.String() + log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from flag") + } + if compiledFacetsPath == "" { + log.Fatal(). + Msg("Compiled facets path is missing. Set it using --compiled-facets flag or COMPILED_FACETS_PATH environment variable") + } + + envReportOutDir := os.Getenv("REPORT_OUT_DIR") + if envReportOutDir != "" { + reportOutDir = envReportOutDir + } + if reportOutDir == "" { + reportOutDir = cmd.Flag("report-out-dir").Value.String() + } + if reportOutDir == "" { + log.Fatal(). + Msg("Report out directory is missing. Set it using --report-out-dir flag or REPORT_OUT_DIR environment variable") + } + return + } + + envDeploymentsPath := os.Getenv("DEPLOYMENTS_PATH") + if envDeploymentsPath != "" { + deploymentsPath = envDeploymentsPath + } + if deploymentsPath == "" { + deploymentsPath = cmd.Flag("deployments").Value.String() + } + if deploymentsPath == "" { + log.Fatal(). + Msg("Deployments path is missing. Set it using --deployments flag or DEPLOYMENTS_PATH environment variable") + } + }, + Run: func(cmd *cobra.Command, args []string) { + verbose, _ := cmd.Flags().GetBool("verbose") + if sourceDiff { + + log.Info(). + Str("facetSourcePath", facetSourcePath). + Str("compiledFacetsPath", compiledFacetsPath). + Msg("Running diff for facet path recursively only compiled facet contracts") + + if err := executeSourceDiff(verbose, facetSourcePath, compiledFacetsPath, sourceDiffDir); err != nil { + log.Fatal().Err(err).Msg("Error executing source diff") + return + } + } else { + + originEnvironment, targetEnvironment = args[0], args[1] + for _, environment := range []string{originEnvironment, targetEnvironment} { + if !utils.Contains(supportedEnvironments, environment) { + log.Fatal().Str("environment", environment).Msg("Environment not supported. Environment can be one of alpha, gamma, or omega.") + } + } + + log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Environment") + + if baseRpcUrl == "" { + baseRpcUrl = os.Getenv("BASE_RPC_URL") + if baseRpcUrl == "" { + log.Fatal().Msg("Base RPC URL not provided. Set it using --base-rpc flag or BASE_RPC_URL environment variable") + } + } + + if baseSepoliaRpcUrl == "" { + baseSepoliaRpcUrl = os.Getenv("BASE_SEPOLIA_RPC_URL") + if baseSepoliaRpcUrl == "" { + log.Fatal().Msg("Base Sepolia RPC URL not provided. Set it using --base-sepolia-rpc flag or BASE_SEPOLIA_RPC_URL environment variable") + } + } + + basescanAPIKey := os.Getenv("BASESCAN_API_KEY") + if basescanAPIKey == "" { + log.Fatal().Msg("BaseScan API key not provided. Set it using BASESCAN_API_KEY environment variable") + } + + log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Running diff for environment") + // Create BaseConfig struct + baseConfig := utils.BaseConfig{ + BaseRpcUrl: baseRpcUrl, + BaseSepoliaRpcUrl: baseSepoliaRpcUrl, + BasescanAPIKey: basescanAPIKey, + } + + if err := executeEnvrionmentDiff(verbose, baseConfig, deploymentsPath, originEnvironment, targetEnvironment, reportOutDir); err != nil { + log.Fatal().Err(err).Msg("Error executing environment diff") + } + } + }, +} + +func executeSourceDiff(verbose bool, facetSourcePath, compiledFacetsPath string, reportOutDir string) error { + facetFiles, err := utils.GetFacetFiles(facetSourcePath) + if err != nil { + log.Error(). + Str("facetSourcePath", facetSourcePath). + Str("compiledFacetsPath", compiledFacetsPath). + Err(err). + Msg("Error getting facet files") + return err + } + log.Debug().Int("facetFilesCount", len(facetFiles)).Msg("Facet files length") + + compiledHashes, err := utils.GetCompiledFacetHashes(compiledFacetsPath, facetFiles) + if err != nil { + log.Error(). + Err(err). + Str("compiledFacetsPath", compiledFacetsPath). + Msg("Error getting compiled facet hashes") + return err + } + + if verbose { + log.Info().Int("compiledHashesCount", len(compiledHashes)).Msg("Compiled Facet Hashes") + for file, hash := range compiledHashes { + log.Info().Str("file", file).Str("hash", hash).Msg("Compiled Facet Hash") + } + } + + err = utils.CreateFacetHashesReport(compiledFacetsPath, compiledHashes, reportOutDir, verbose) + if err != nil { + log.Error().Err(err).Msg("Error creating facet hashes report") + return err + } + + return nil +} + +func executeEnvrionmentDiff( + verbose bool, + baseConfig utils.BaseConfig, + deploymentsPath, originEnvironment, targetEnvironment string, + reportOutDir string, +) error { + // walk environment diamonds and get all facet addresses from DiamondLoupe facet view + baseDiamonds := []utils.Diamond{ + utils.BaseRegistry, + utils.Space, + utils.SpaceFactory, + utils.SpaceOwner, + } + originDeploymentsPath := filepath.Join(deploymentsPath, originEnvironment) + originDiamonds, err := utils.GetDiamondAddresses(originDeploymentsPath, baseDiamonds, verbose) + if err != nil { + log.Error().Err(err).Msgf("Error getting diamond addresses for origin environment %s", originEnvironment) + return err + } + targetDeploymentsPath := filepath.Join(deploymentsPath, targetEnvironment) + targetDiamonds, err := utils.GetDiamondAddresses(targetDeploymentsPath, baseDiamonds, verbose) + if err != nil { + log.Error().Err(err).Msgf("Error getting diamond addresses for target environment %s", targetEnvironment) + return err + } + // Create Ethereum client + clients, err := utils.CreateEthereumClients( + baseConfig.BaseRpcUrl, + baseConfig.BaseSepoliaRpcUrl, + originEnvironment, + targetEnvironment, + verbose, + ) + defer func() { + for _, client := range clients { + client.Close() + } + }() + // getCode for all facet addresses over base rpc url and compare with compiled hashes + originFacets := make(map[string][]utils.Facet) + + for diamondName, diamondAddress := range originDiamonds { + facets, err := utils.ReadAllFacets(clients[originEnvironment], diamondAddress, baseConfig.BasescanAPIKey) + if err != nil { + log.Error().Err(err).Msgf("Error reading all facets for origin diamond %s", diamondName) + return err + } + err = utils.AddContractCodeHashes(clients[originEnvironment], facets) + if err != nil { + log.Error().Err(err).Msgf("Error adding contract code hashes for origin diamond %s", diamondName) + return err + } + originFacets[string(diamondName)] = facets + } + + targetFacets := make(map[string][]utils.Facet) + for diamondName, diamondAddress := range targetDiamonds { + facets, err := utils.ReadAllFacets(clients[targetEnvironment], diamondAddress, baseConfig.BasescanAPIKey) + if err != nil { + log.Error().Err(err).Msgf("Error reading all facets for target diamond %s", diamondName) + return err + } + err = utils.AddContractCodeHashes(clients[targetEnvironment], facets) + if err != nil { + log.Error().Err(err).Msgf("Error adding contract code hashes for target diamond %s", diamondName) + return err + } + targetFacets[string(diamondName)] = facets + } + if verbose { + for diamondName, facets := range originFacets { + log.Info().Str("diamondName", diamondName).Msg("Origin Facets for Diamond contract") + for _, facet := range facets { + log.Info(). + Str("facetAddress", facet.FacetAddress.Hex()). + Str("contractName", facet.ContractName). + Interface("selectors", facet.SelectorsHex). + Msg("Facet") + } + } + for diamondName, facets := range targetFacets { + log.Info().Str("diamondName", diamondName).Msg("Target Facets for Diamond contract") + for _, facet := range facets { + log.Info(). + Str("facetAddress", facet.FacetAddress.Hex()). + Str("contractName", facet.ContractName). + Interface("selectors", facet.SelectorsHex). + Msg("Facet") + } + } + } + + // compare facets and create report + differences := utils.CompareFacets(originFacets, targetFacets) + if verbose { + for diamondName, facets := range differences { + log.Info().Str("diamondName", diamondName).Msg("Differences for Diamond contract") + for _, facet := range facets { + log.Info(). + Str("facetAddress", facet.OriginContractAddress.Hex()). + Str("originContractName", facet.OriginContractName). + Msg("Origin Facet") + log.Info(). + Interface("selectorDiff", facet.SelectorsDiff). + Msg("Selector Diff") + + } + } + } + + // create report + log.Info().Str("reportOutDir", reportOutDir).Msg("Generating YAML report") + err = utils.GenerateYAMLReport(originEnvironment, targetEnvironment, differences, reportOutDir) + if err != nil { + log.Error().Err(err).Msg("Error generating YAML report") + return err + } + return nil +} diff --git a/scripts/bytecode-diff/cmd/run_util.go b/scripts/bytecode-diff/cmd/run_util.go new file mode 100644 index 000000000..af2ea5020 --- /dev/null +++ b/scripts/bytecode-diff/cmd/run_util.go @@ -0,0 +1,121 @@ +package cmd + +import ( + "bytecode-diff/utils" + "fmt" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +var AddHashesCmd = &cobra.Command{ + Use: "add-hashes [environment] [yaml_file_path]", + Short: "Add bytecode hashes to a YAML file for a specific environment", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + environment := args[0] + yamlFilePath := args[1] + + supportedEnvironments := []string{"alpha", "gamma", "omega"} + if !utils.Contains(supportedEnvironments, environment) { + log.Fatal(). + Str("environment", environment). + Msg("Environment not supported. Environment can be one of alpha, gamma, or omega.") + } + + if baseRpcUrl == "" { + log.Fatal(). + Msg("Base RPC URL not provided. Set it using --base-rpc-url flag or BASE_RPC_URL environment variable") + } + + if baseSepoliaRpcUrl == "" { + log.Fatal(). + Msg("Base Sepolia RPC URL not provided. Set it using --base-sepolia-rpc-url flag or BASE_SEPOLIA_RPC_URL environment variable") + } + + // Create Ethereum client + clients, err := utils.CreateEthereumClients( + baseRpcUrl, + baseSepoliaRpcUrl, + environment, + "", + false, + ) + if err != nil { + log.Fatal().Err(err).Msg("Failed to create Ethereum client") + } + defer clients[environment].Close() + + // Read YAML file + yamlData, err := os.ReadFile(yamlFilePath) + if err != nil { + log.Fatal().Err(err).Str("file", yamlFilePath).Msg("Failed to read YAML file") + } + + var data map[string]interface{} + err = yaml.Unmarshal(yamlData, &data) + if err != nil { + log.Fatal().Err(err).Msg("Failed to unmarshal YAML data") + } + + // Process deployments + deployments, ok := data["deployments"].(map[string]interface{}) + if !ok { + log.Fatal().Msg("Invalid YAML structure: 'deployments' field not found or not a map") + } + + for name, deployment := range deployments { + deploymentMap, ok := deployment.(map[string]interface{}) + if !ok { + log.Warn().Str("name", name).Msg("Skipping invalid deployment entry") + continue + } + + address, ok := deploymentMap["address"].(string) + if !ok { + log.Warn().Str("name", name).Msg("Skipping deployment without valid address") + continue + } + + addressBytes := common.HexToAddress(address) + hash, err := utils.GetContractCodeHash(clients[environment], addressBytes) + if err != nil { + log.Error().Err(err).Str("name", name).Str("address", address).Msg("Failed to get contract code hash") + continue + } + + deploymentMap["bytecodeHash"] = hash + } + + // Write updated YAML file + outputPath := filepath.Join( + filepath.Dir(yamlFilePath), + fmt.Sprintf( + "%s_hashed.yaml", + filepath.Base(yamlFilePath[:len(yamlFilePath)-len(filepath.Ext(yamlFilePath))]), + ), + ) + + updatedYAML, err := yaml.Marshal(data) + if err != nil { + log.Fatal().Err(err).Msg("Failed to marshal updated YAML data") + } + + err = os.WriteFile(outputPath, updatedYAML, 0644) + if err != nil { + log.Fatal().Err(err).Str("file", outputPath).Msg("Failed to write updated YAML file") + } + + log.Info().Str("file", outputPath).Msg("Successfully wrote updated YAML file with bytecode hashes") + }, +} + +func init() { + AddHashesCmd.Flags().StringVar(&baseRpcUrl, "base-rpc-url", os.Getenv("BASE_RPC_URL"), "Base RPC URL") + AddHashesCmd.Flags(). + StringVar(&baseSepoliaRpcUrl, "base-sepolia-rpc-url", os.Getenv("BASE_SEPOLIA_RPC_URL"), "Base Sepolia RPC URL") +} diff --git a/scripts/bytecode-diff/go.mod b/scripts/bytecode-diff/go.mod index f5cf6599b..af385086a 100644 --- a/scripts/bytecode-diff/go.mod +++ b/scripts/bytecode-diff/go.mod @@ -8,6 +8,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.1 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( diff --git a/scripts/bytecode-diff/main.go b/scripts/bytecode-diff/main.go index a3619bc30..35d7a803c 100644 --- a/scripts/bytecode-diff/main.go +++ b/scripts/bytecode-diff/main.go @@ -1,359 +1,7 @@ package main -import ( - "bytecode-diff/utils" - "fmt" - "os" - "path/filepath" - - "github.com/joho/godotenv" - "github.com/rs/zerolog" - "github.com/spf13/cobra" -) - -var log zerolog.Logger - -func init() { - log = zerolog.New(os.Stderr).With().Timestamp().Logger() - utils.SetLogger(log) -} - -func setLogLevel(level string) { - switch level { - case "debug": - zerolog.SetGlobalLevel(zerolog.DebugLevel) - case "info": - zerolog.SetGlobalLevel(zerolog.InfoLevel) - case "warn": - zerolog.SetGlobalLevel(zerolog.WarnLevel) - case "error": - zerolog.SetGlobalLevel(zerolog.ErrorLevel) - default: - zerolog.SetGlobalLevel(zerolog.InfoLevel) - } -} +import "bytecode-diff/cmd" func main() { - if err := godotenv.Load(); err != nil { - log.Warn().Msg("No .env file found") - } - - supportedEnvironments := []string{"alpha", "gamma", "omega"} - var baseRpcUrl string - var facetSourcePath string - var compiledFacetsPath string - var sourceDiffDir string - var sourceDiff bool - var reportOutDir string - var originEnvironment, targetEnvironment string - var deploymentsPath string - var baseSepoliaRpcUrl string - var logLevel string - - rootCmd := &cobra.Command{ - Use: "bytecode-diff [origin_environment] [target_environment]", - Short: "A tool to retrieve and display contract bytecode diff for Base", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - Args: func(cmd *cobra.Command, args []string) error { - if sourceDiff { - if len(args) != 0 { - return fmt.Errorf("no positional arguments expected when --source-diff-only is set") - } - } else { - if len(args) < 2 { - return fmt.Errorf("at least two arguments required when --source-diff-only is not set, [origin_environment], [target_environment]") - } - } - return nil - }, - PreRun: func(cmd *cobra.Command, args []string) { - if sourceDiff { - envSourceDiffDir := os.Getenv("SOURCE_DIFF_DIR") - if envSourceDiffDir != "" { - sourceDiffDir = envSourceDiffDir - } - - if sourceDiffDir == "" { - sourceDiffDir = cmd.Flag("source-diff-log").Value.String() - } - - facetSourcePath = os.Getenv("FACET_SOURCE_PATH") - if facetSourcePath == "" { - facetSourcePath = cmd.Flag("facets").Value.String() - } - if facetSourcePath == "" { - log.Fatal(). - Msg("Facet source path is missing. Set it using --facets flag or FACET_SOURCE_PATH environment variable") - } - - compiledFacetsPath = os.Getenv("COMPILED_FACETS_PATH") - log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from environment") - if compiledFacetsPath == "" { - compiledFacetsPath = cmd.Flag("compiled-facets").Value.String() - log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from flag") - } - if compiledFacetsPath == "" { - log.Fatal(). - Msg("Compiled facets path is missing. Set it using --compiled-facets flag or COMPILED_FACETS_PATH environment variable") - } - - envReportOutDir := os.Getenv("REPORT_OUT_DIR") - if envReportOutDir != "" { - reportOutDir = envReportOutDir - } - if reportOutDir == "" { - reportOutDir = cmd.Flag("report-out-dir").Value.String() - } - if reportOutDir == "" { - log.Fatal(). - Msg("Report out directory is missing. Set it using --report-out-dir flag or REPORT_OUT_DIR environment variable") - } - return - } - - envDeploymentsPath := os.Getenv("DEPLOYMENTS_PATH") - if envDeploymentsPath != "" { - deploymentsPath = envDeploymentsPath - } - if deploymentsPath == "" { - deploymentsPath = cmd.Flag("deployments").Value.String() - } - if deploymentsPath == "" { - log.Fatal(). - Msg("Deployments path is missing. Set it using --deployments flag or DEPLOYMENTS_PATH environment variable") - } - }, - Run: func(cmd *cobra.Command, args []string) { - verbose, _ := cmd.Flags().GetBool("verbose") - if sourceDiff { - - log.Info(). - Str("facetSourcePath", facetSourcePath). - Str("compiledFacetsPath", compiledFacetsPath). - Msg("Running diff for facet path recursively only compiled facet contracts") - - if err := executeSourceDiff(verbose, facetSourcePath, compiledFacetsPath, sourceDiffDir); err != nil { - log.Fatal().Err(err).Msg("Error executing source diff") - return - } - } else { - - originEnvironment, targetEnvironment = args[0], args[1] - for _, environment := range []string{originEnvironment, targetEnvironment} { - if !utils.Contains(supportedEnvironments, environment) { - log.Fatal().Str("environment", environment).Msg("Environment not supported. Environment can be one of alpha, gamma, or omega.") - } - } - - log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Environment") - - if baseRpcUrl == "" { - baseRpcUrl = os.Getenv("BASE_RPC_URL") - if baseRpcUrl == "" { - log.Fatal().Msg("Base RPC URL not provided. Set it using --base-rpc flag or BASE_RPC_URL environment variable") - } - } - - if baseSepoliaRpcUrl == "" { - baseSepoliaRpcUrl = os.Getenv("BASE_SEPOLIA_RPC_URL") - if baseSepoliaRpcUrl == "" { - log.Fatal().Msg("Base Sepolia RPC URL not provided. Set it using --base-sepolia-rpc flag or BASE_SEPOLIA_RPC_URL environment variable") - } - } - - basescanAPIKey := os.Getenv("BASESCAN_API_KEY") - if basescanAPIKey == "" { - log.Fatal().Msg("BaseScan API key not provided. Set it using BASESCAN_API_KEY environment variable") - } - - log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Running diff for environment") - // Create BaseConfig struct - baseConfig := utils.BaseConfig{ - BaseRpcUrl: baseRpcUrl, - BaseSepoliaRpcUrl: baseSepoliaRpcUrl, - BasescanAPIKey: basescanAPIKey, - } - - if err := executeEnvrionmentDiff(verbose, baseConfig, deploymentsPath, originEnvironment, targetEnvironment, reportOutDir); err != nil { - log.Fatal().Err(err).Msg("Error executing environment diff") - } - } - }, - } - rootCmd.Flags().StringVarP(&baseRpcUrl, "base-rpc", "b", "", "Base RPC provider URL") - rootCmd.Flags().StringVarP(&baseSepoliaRpcUrl, "base-sepolia-rpc", "", "", "Base Sepolia RPC provider URL") - rootCmd.Flags().BoolVarP(&sourceDiff, "source-diff-only", "s", false, "Run source code diff") - rootCmd.Flags().StringVar(&sourceDiffDir, "source-diff-log", "source-diffs", "Path to diff log file") - rootCmd.Flags().StringVar(&compiledFacetsPath, "compiled-facets", "../../contracts/out", "Path to compiled facets") - rootCmd.Flags().StringVar(&facetSourcePath, "facets", "", "Path to facet source files") - rootCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output") - rootCmd.Flags().StringVar(&reportOutDir, "report-out-dir", "deployed-diffs", "Path to report output directory") - rootCmd.Flags(). - StringVar(&deploymentsPath, "deployments", "../../contracts/deployments", "Path to deployments directory") - rootCmd.PersistentFlags(). - StringVar(&logLevel, "log-level", "info", "Set the logging level (debug, info, warn, error)") - - if err := rootCmd.Execute(); err != nil { - log.Error().Err(err).Msg("Error executing root command") - os.Exit(1) - } -} - -func executeSourceDiff(verbose bool, facetSourcePath, compiledFacetsPath string, reportOutDir string) error { - facetFiles, err := utils.GetFacetFiles(facetSourcePath) - if err != nil { - log.Error(). - Str("facetSourcePath", facetSourcePath). - Str("compiledFacetsPath", compiledFacetsPath). - Err(err). - Msg("Error getting facet files") - return err - } - log.Debug().Int("facetFilesCount", len(facetFiles)).Msg("Facet files length") - - compiledHashes, err := utils.GetCompiledFacetHashes(compiledFacetsPath, facetFiles) - if err != nil { - log.Error(). - Err(err). - Str("compiledFacetsPath", compiledFacetsPath). - Msg("Error getting compiled facet hashes") - return err - } - - if verbose { - log.Info().Int("compiledHashesCount", len(compiledHashes)).Msg("Compiled Facet Hashes") - for file, hash := range compiledHashes { - log.Info().Str("file", file).Str("hash", hash).Msg("Compiled Facet Hash") - } - } - - err = utils.CreateFacetHashesReport(compiledFacetsPath, compiledHashes, reportOutDir, verbose) - if err != nil { - log.Error().Err(err).Msg("Error creating facet hashes report") - return err - } - - return nil -} - -func executeEnvrionmentDiff( - verbose bool, - baseConfig utils.BaseConfig, - deploymentsPath, originEnvironment, targetEnvironment string, - reportOutDir string, -) error { - // walk environment diamonds and get all facet addresses from DiamondLoupe facet view - baseDiamonds := []utils.Diamond{ - utils.BaseRegistry, - utils.Space, - utils.SpaceFactory, - utils.SpaceOwner, - } - originDeploymentsPath := filepath.Join(deploymentsPath, originEnvironment) - originDiamonds, err := utils.GetDiamondAddresses(originDeploymentsPath, baseDiamonds, verbose) - if err != nil { - log.Error().Err(err).Msgf("Error getting diamond addresses for origin environment %s", originEnvironment) - return err - } - targetDeploymentsPath := filepath.Join(deploymentsPath, targetEnvironment) - targetDiamonds, err := utils.GetDiamondAddresses(targetDeploymentsPath, baseDiamonds, verbose) - if err != nil { - log.Error().Err(err).Msgf("Error getting diamond addresses for target environment %s", targetEnvironment) - return err - } - // Create Ethereum client - clients, err := utils.CreateEthereumClients( - baseConfig.BaseRpcUrl, - baseConfig.BaseSepoliaRpcUrl, - originEnvironment, - targetEnvironment, - verbose, - ) - defer func() { - for _, client := range clients { - client.Close() - } - }() - // getCode for all facet addresses over base rpc url and compare with compiled hashes - originFacets := make(map[string][]utils.Facet) - - for diamondName, diamondAddress := range originDiamonds { - facets, err := utils.ReadAllFacets(clients[originEnvironment], diamondAddress, baseConfig.BasescanAPIKey) - if err != nil { - log.Error().Err(err).Msgf("Error reading all facets for origin diamond %s", diamondName) - return err - } - err = utils.AddContractCodeHashes(clients[originEnvironment], facets) - if err != nil { - log.Error().Err(err).Msgf("Error adding contract code hashes for origin diamond %s", diamondName) - return err - } - originFacets[string(diamondName)] = facets - } - - targetFacets := make(map[string][]utils.Facet) - for diamondName, diamondAddress := range targetDiamonds { - facets, err := utils.ReadAllFacets(clients[targetEnvironment], diamondAddress, baseConfig.BasescanAPIKey) - if err != nil { - log.Error().Err(err).Msgf("Error reading all facets for target diamond %s", diamondName) - return err - } - err = utils.AddContractCodeHashes(clients[targetEnvironment], facets) - if err != nil { - log.Error().Err(err).Msgf("Error adding contract code hashes for target diamond %s", diamondName) - return err - } - targetFacets[string(diamondName)] = facets - } - if verbose { - for diamondName, facets := range originFacets { - log.Info().Str("diamondName", diamondName).Msg("Origin Facets for Diamond contract") - for _, facet := range facets { - log.Info(). - Str("facetAddress", facet.FacetAddress.Hex()). - Str("contractName", facet.ContractName). - Interface("selectors", facet.SelectorsHex). - Msg("Facet") - } - } - for diamondName, facets := range targetFacets { - log.Info().Str("diamondName", diamondName).Msg("Target Facets for Diamond contract") - for _, facet := range facets { - log.Info(). - Str("facetAddress", facet.FacetAddress.Hex()). - Str("contractName", facet.ContractName). - Interface("selectors", facet.SelectorsHex). - Msg("Facet") - } - } - } - - // compare facets and create report - differences := utils.CompareFacets(originFacets, targetFacets) - if verbose { - for diamondName, facets := range differences { - log.Info().Str("diamondName", diamondName).Msg("Differences for Diamond contract") - for _, facet := range facets { - log.Info(). - Str("facetAddress", facet.OriginContractAddress.Hex()). - Str("originContractName", facet.OriginContractName). - Msg("Origin Facet") - log.Info(). - Interface("selectorDiff", facet.SelectorsDiff). - Msg("Selector Diff") - - } - } - } - - // create report - log.Info().Str("reportOutDir", reportOutDir).Msg("Generating YAML report") - err = utils.GenerateYAMLReport(originEnvironment, targetEnvironment, differences, reportOutDir) - if err != nil { - log.Error().Err(err).Msg("Error generating YAML report") - return err - } - return nil + cmd.Execute() } diff --git a/scripts/bytecode-diff/utils/ethereum.go b/scripts/bytecode-diff/utils/ethereum.go index a717dc731..c83e850ba 100644 --- a/scripts/bytecode-diff/utils/ethereum.go +++ b/scripts/bytecode-diff/utils/ethereum.go @@ -19,7 +19,7 @@ import ( type Facet struct { FacetAddress common.Address Selectors [][4]byte `json:",omitempty"` - SelectorsHex []string `abi:"-"` + SelectorsHex []string ` abi:"-"` ContractName string `json:",omitempty"` BytecodeHash string `json:",omitempty"` } @@ -127,7 +127,10 @@ func ReadAllFacets(client *ethclient.Client, contractAddress string, basescanAPI return facets, nil } -func CreateEthereumClients(baseRpcUrl, baseSepoliaRpcUrl, originEnvironment, targetEnvironment string, verbose bool) (map[string]*ethclient.Client, error) { +func CreateEthereumClients( + baseRpcUrl, baseSepoliaRpcUrl, originEnvironment, targetEnvironment string, + verbose bool, +) (map[string]*ethclient.Client, error) { clients := make(map[string]*ethclient.Client) for _, env := range []string{originEnvironment, targetEnvironment} { @@ -209,20 +212,26 @@ func GetContractNameFromBasescan(baseURL, address, apiKey string) (string, error return result.Result[0].ContractName, nil } +// GetContractCodeHash fetches the deployed code and calculates its keccak256 hash +func GetContractCodeHash(client *ethclient.Client, address common.Address) (string, error) { + code, err := client.CodeAt(context.Background(), address, nil) + if err != nil { + return "", fmt.Errorf("failed to read contract code for address %s: %w", address.Hex(), err) + } + + hash := crypto.Keccak256Hash(code) + return hash.Hex(), nil +} + // AddContractCodeHashes reads the contract code for each facet and adds its keccak256 hash to the Facet struct func AddContractCodeHashes(client *ethclient.Client, facets []Facet) error { for i, facet := range facets { - // Read the contract code - code, err := client.CodeAt(context.Background(), facet.FacetAddress, nil) + hash, err := GetContractCodeHash(client, facet.FacetAddress) if err != nil { - return fmt.Errorf("failed to read contract code for address %s: %w", facet.FacetAddress.Hex(), err) + return err } - // Hash the code using Keccak256Hash - hash := crypto.Keccak256Hash(code) - - // Store the hash hex string in the Facet struct - facets[i].BytecodeHash = hash.Hex() + facets[i].BytecodeHash = hash } return nil From a43181b117e5a4ae1cb575ff1bf89483de175744 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Fri, 6 Sep 2024 16:04:53 -0700 Subject: [PATCH 03/30] render diff update report to html --- scripts/bytecode-diff/cmd/run_util.go | 156 ++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 7 deletions(-) diff --git a/scripts/bytecode-diff/cmd/run_util.go b/scripts/bytecode-diff/cmd/run_util.go index af2ea5020..5bbefe56d 100644 --- a/scripts/bytecode-diff/cmd/run_util.go +++ b/scripts/bytecode-diff/cmd/run_util.go @@ -3,8 +3,11 @@ package cmd import ( "bytecode-diff/utils" "fmt" + "html/template" "os" "path/filepath" + "strings" + "time" "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog/log" @@ -12,6 +15,21 @@ import ( "gopkg.in/yaml.v3" ) +var htmlRender bool + +func getIncrementedFileName(basePath string, extension string) string { + dir := filepath.Dir(basePath) + fileName := filepath.Base(basePath[:len(basePath)-len(filepath.Ext(basePath))]) + + for i := 1; ; i++ { + newFileName := fmt.Sprintf("%s_hashed_%d%s", fileName, i, extension) + fullPath := filepath.Join(dir, newFileName) + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + return fullPath + } + } +} + var AddHashesCmd = &cobra.Command{ Use: "add-hashes [environment] [yaml_file_path]", Short: "Add bytecode hashes to a YAML file for a specific environment", @@ -92,13 +110,7 @@ var AddHashesCmd = &cobra.Command{ } // Write updated YAML file - outputPath := filepath.Join( - filepath.Dir(yamlFilePath), - fmt.Sprintf( - "%s_hashed.yaml", - filepath.Base(yamlFilePath[:len(yamlFilePath)-len(filepath.Ext(yamlFilePath))]), - ), - ) + outputPath := getIncrementedFileName(yamlFilePath, ".yaml") updatedYAML, err := yaml.Marshal(data) if err != nil { @@ -110,12 +122,142 @@ var AddHashesCmd = &cobra.Command{ log.Fatal().Err(err).Str("file", outputPath).Msg("Failed to write updated YAML file") } + // After writing the updated YAML file + if htmlRender { + htmlContent, err := renderYAMLToHTML(updatedYAML) + if err != nil { + log.Error().Err(err).Msg("Failed to render YAML to HTML") + } else { + htmlOutputPath := getIncrementedFileName(yamlFilePath, ".html") + err = os.WriteFile(htmlOutputPath, []byte(htmlContent), 0644) + if err != nil { + log.Error().Err(err).Str("file", htmlOutputPath).Msg("Failed to write HTML file") + } else { + log.Info().Str("file", htmlOutputPath).Msg("Successfully wrote HTML file with bytecode hashes") + } + } + } + log.Info().Str("file", outputPath).Msg("Successfully wrote updated YAML file with bytecode hashes") }, } +func renderYAMLToHTML(yamlData []byte) (string, error) { + var data map[string]interface{} + err := yaml.Unmarshal(yamlData, &data) + if err != nil { + return "", fmt.Errorf("failed to unmarshal YAML data: %w", err) + } + + log.Info().Interface("data", data).Msg("YAML data structure") + + tmpl := ` + + + + + + YAML Content with Bytecode Hashes + + + +

YAML Content with Bytecode Hashes

+

Generated on: {{.currentTime}}

+ +

Deployments

+ + + + + + + + + {{range $name, $deployment := .deployments}} + + + + + + + + {{end}} +
NameAddressBytecode HashBasescan LinkDeployment Date
{{$name}}{{$deployment.address}}{{$deployment.bytecodeHash}}View on Basescan{{$deployment.deploymentDate}}
+ +

Diamonds

+ {{if .diamonds}} + {{range $name, $diamond := .diamonds}} +

Diamond: {{$name}}

+

Facets

+ {{if $diamond.facets}} + + + + + + + + + {{range $facet := $diamond.facets}} + + + + + + + + {{end}} +
Contract NameOrigin AddressOrigin Bytecode HashTarget AddressesTarget Bytecode Hashes
{{$facet.originContractName}}{{$facet.originFacetAddress}}{{$facet.originBytecodeHash}} + {{range $addr := $facet.targetContractAddresses}} + {{$addr}}
+ {{end}} +
+ {{range $hash := $facet.targetBytecodeHashes}} + {{$hash}}
+ {{end}} +
+ {{else}} +

No facets found for this diamond.

+ {{end}} + {{end}} + {{else}} +

No diamonds found in the YAML data.

+ {{end}} + + {{range $key, $value := .}} + {{if and (ne $key "deployments") (ne $key "diamonds")}} +

{{$key}}

+
{{$value | printf "%#v"}}
+ {{end}} + {{end}} + +` + + t, err := template.New("yaml2html").Parse(tmpl) + if err != nil { + return "", fmt.Errorf("failed to parse HTML template: %w", err) + } + + data["currentTime"] = time.Now().UTC().Format(time.RFC3339) + + var buf strings.Builder + err = t.Execute(&buf, data) + if err != nil { + return "", fmt.Errorf("failed to execute HTML template: %w", err) + } + + return buf.String(), nil +} + func init() { AddHashesCmd.Flags().StringVar(&baseRpcUrl, "base-rpc-url", os.Getenv("BASE_RPC_URL"), "Base RPC URL") AddHashesCmd.Flags(). StringVar(&baseSepoliaRpcUrl, "base-sepolia-rpc-url", os.Getenv("BASE_SEPOLIA_RPC_URL"), "Base Sepolia RPC URL") + AddHashesCmd.Flags().BoolVar(&htmlRender, "html-render", true, "Render output as HTML") } From 49d715ad953e4c268ea15b9dc52bb722b104f613 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Fri, 6 Sep 2024 20:29:10 -0700 Subject: [PATCH 04/30] updated run_util cmd to render html report --- scripts/bytecode-diff/cmd/run_util.go | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/scripts/bytecode-diff/cmd/run_util.go b/scripts/bytecode-diff/cmd/run_util.go index 5bbefe56d..8c0b5b369 100644 --- a/scripts/bytecode-diff/cmd/run_util.go +++ b/scripts/bytecode-diff/cmd/run_util.go @@ -32,7 +32,7 @@ func getIncrementedFileName(basePath string, extension string) string { var AddHashesCmd = &cobra.Command{ Use: "add-hashes [environment] [yaml_file_path]", - Short: "Add bytecode hashes to a YAML file for a specific environment", + Short: "Add bytecode hashes and render yaml, html reports", Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { environment := args[0] @@ -124,7 +124,7 @@ var AddHashesCmd = &cobra.Command{ // After writing the updated YAML file if htmlRender { - htmlContent, err := renderYAMLToHTML(updatedYAML) + htmlContent, err := renderYAMLToHTML(updatedYAML, environment) if err != nil { log.Error().Err(err).Msg("Failed to render YAML to HTML") } else { @@ -142,14 +142,15 @@ var AddHashesCmd = &cobra.Command{ }, } -func renderYAMLToHTML(yamlData []byte) (string, error) { +func renderYAMLToHTML(yamlData []byte, environment string) (string, error) { var data map[string]interface{} err := yaml.Unmarshal(yamlData, &data) if err != nil { return "", fmt.Errorf("failed to unmarshal YAML data: %w", err) } - log.Info().Interface("data", data).Msg("YAML data structure") + data["environment"] = environment + data["currentTime"] = time.Now().UTC().Format(time.RFC3339) tmpl := ` @@ -157,17 +158,18 @@ func renderYAMLToHTML(yamlData []byte) (string, error) { - YAML Content with Bytecode Hashes + Base Facets Bytecode Diff Report -

YAML Content with Bytecode Hashes

+

{{.environment}}: Base Facets Bytecode Diff Report

Generated on: {{.currentTime}}

Deployments

@@ -193,7 +195,7 @@ func renderYAMLToHTML(yamlData []byte) (string, error) {

Diamonds

{{if .diamonds}} {{range $name, $diamond := .diamonds}} -

Diamond: {{$name}}

+

Diamond: {{$diamond.name}}

Facets

{{if $diamond.facets}} @@ -203,6 +205,10 @@ func renderYAMLToHTML(yamlData []byte) (string, error) { + + + + {{range $facet := $diamond.facets}} @@ -219,6 +225,9 @@ func renderYAMLToHTML(yamlData []byte) (string, error) { {{$hash}}
{{end}} + + + {{end}}
Origin Bytecode Hash Target Addresses Target Bytecode HashesSelectors Missing on {{.environment}}Origin VerifiedTarget Verified
{{$facet.selectorsDiff}}{{$facet.originVerified}}{{$facet.targetVerified}}
@@ -244,8 +253,6 @@ func renderYAMLToHTML(yamlData []byte) (string, error) { return "", fmt.Errorf("failed to parse HTML template: %w", err) } - data["currentTime"] = time.Now().UTC().Format(time.RFC3339) - var buf strings.Builder err = t.Execute(&buf, data) if err != nil { From 7abde26cfb2489d950c56212cea99ba9e2e3f585 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sat, 7 Sep 2024 14:17:11 -0700 Subject: [PATCH 05/30] clean up report.html template and move to separate dir --- .prettierignore | 1 + scripts/bytecode-diff/cmd/run_util.go | 100 +---------------- scripts/bytecode-diff/templates/report.html | 118 ++++++++++++++++++++ 3 files changed, 121 insertions(+), 98 deletions(-) create mode 100644 scripts/bytecode-diff/templates/report.html diff --git a/.prettierignore b/.prettierignore index 38a630a2a..b13b94b60 100644 --- a/.prettierignore +++ b/.prettierignore @@ -47,4 +47,5 @@ yarn.lock packages/generated/* +scripts/bytecode-diff/templates/* packages/proto/src/protocol_*.ts diff --git a/scripts/bytecode-diff/cmd/run_util.go b/scripts/bytecode-diff/cmd/run_util.go index 8c0b5b369..7c1ea0918 100644 --- a/scripts/bytecode-diff/cmd/run_util.go +++ b/scripts/bytecode-diff/cmd/run_util.go @@ -150,105 +150,9 @@ func renderYAMLToHTML(yamlData []byte, environment string) (string, error) { } data["environment"] = environment - data["currentTime"] = time.Now().UTC().Format(time.RFC3339) + data["reportTime"] = time.Now().UTC().Format(time.RFC3339) - tmpl := ` - - - - - - Base Facets Bytecode Diff Report - - - -

{{.environment}}: Base Facets Bytecode Diff Report

-

Generated on: {{.currentTime}}

- -

Deployments

- - - - - - - - - {{range $name, $deployment := .deployments}} - - - - - - - - {{end}} -
NameAddressBytecode HashBasescan LinkDeployment Date
{{$name}}{{$deployment.address}}{{$deployment.bytecodeHash}}View on Basescan{{$deployment.deploymentDate}}
- -

Diamonds

- {{if .diamonds}} - {{range $name, $diamond := .diamonds}} -

Diamond: {{$diamond.name}}

-

Facets

- {{if $diamond.facets}} - - - - - - - - - - - - - {{range $facet := $diamond.facets}} - - - - - - - - - - - {{end}} -
Contract NameOrigin AddressOrigin Bytecode HashTarget AddressesTarget Bytecode HashesSelectors Missing on {{.environment}}Origin VerifiedTarget Verified
{{$facet.originContractName}}{{$facet.originFacetAddress}}{{$facet.originBytecodeHash}} - {{range $addr := $facet.targetContractAddresses}} - {{$addr}}
- {{end}} -
- {{range $hash := $facet.targetBytecodeHashes}} - {{$hash}}
- {{end}} -
{{$facet.selectorsDiff}}{{$facet.originVerified}}{{$facet.targetVerified}}
- {{else}} -

No facets found for this diamond.

- {{end}} - {{end}} - {{else}} -

No diamonds found in the YAML data.

- {{end}} - - {{range $key, $value := .}} - {{if and (ne $key "deployments") (ne $key "diamonds")}} -

{{$key}}

-
{{$value | printf "%#v"}}
- {{end}} - {{end}} - -` - - t, err := template.New("yaml2html").Parse(tmpl) + t, err := template.ParseFiles("templates/report.html") if err != nil { return "", fmt.Errorf("failed to parse HTML template: %w", err) } diff --git a/scripts/bytecode-diff/templates/report.html b/scripts/bytecode-diff/templates/report.html new file mode 100644 index 000000000..acd743623 --- /dev/null +++ b/scripts/bytecode-diff/templates/report.html @@ -0,0 +1,118 @@ + + + + + + Base Facets Bytecode Diff Report + + + +

{{.environment}}: Base Facets Bytecode Diff Report

+

Generated on: {{.reportTime}}

+ +

Deployments

+ + + + + + + + + {{range $name, $deployment := .deployments}} + + + + + + + + {{end}} +
NameAddressBytecode HashBasescan LinkDeployment Date
{{$name}} + {{if eq $.environment "gamma"}} + {{$deployment.address}} + {{else if eq $.environment "omega"}} + {{$deployment.address}} + {{else}} + {{$deployment.address}} + {{end}} + {{$deployment.bytecodeHash}}View on Basescan{{$deployment.deploymentDate}}
+ +

Diamonds

+ {{if .diamonds}} + {{range $name, $diamond := .diamonds}} +

Diamond: {{$diamond.name}}

+

Facets

+ {{if $diamond.facets}} + + + + + + + + + + + + + {{range $facet := $diamond.facets}} + + + + + + + + + + + {{end}} +
Contract NameOrigin AddressOrigin Bytecode HashTarget AddressesTarget Bytecode HashesSelectors Missing on {{$.environment}}Origin VerifiedTarget Verified
{{$facet.originContractName}} + {{if eq $.environment "gamma"}} + {{$facet.originFacetAddress}} + {{else if eq $.environment "omega"}} + {{$facet.originFacetAddress}} + {{else}} + {{$facet.originFacetAddress}} + {{end}} + {{$facet.originBytecodeHash}} + {{range $addr := $facet.targetContractAddresses}} + {{if eq $.environment "gamma"}} + {{$addr}}
+ {{else if eq $.environment "omega"}} + {{$addr}}
+ {{else}} + {{$addr}}
+ {{end}} + {{end}} +
+ {{range $hash := $facet.targetBytecodeHashes}} + {{$hash}}
+ {{end}} +
{{$facet.selectorsDiff}}{{$facet.originVerified}}{{$facet.targetVerified}}
+ {{else}} +

No facets found for this diamond.

+ {{end}} + {{end}} + {{else}} +

No diamonds found in the YAML data.

+ {{end}} + + {{range $key, $value := .}} + {{if and (ne $key "deployments") (ne $key "diamonds")}} +

{{$key}}

+
{{$value | printf "%#v"}}
+ {{end}} + {{end}} + + \ No newline at end of file From 6489e9ede9e3631cce980844a270bbdf359af363 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sat, 7 Sep 2024 16:36:08 -0700 Subject: [PATCH 06/30] complete implementation ci job for alpha, gamma report --- .github/workflows/Bytecode_diff_report.yml | 71 +++++++++++++++++ .github/workflows/Network_diff_upgrade.yml | 9 ++- .../workflows/Render_diff_upgrade_report.yml | 79 +++++++++++++++++++ .github/workflows/Upgrade_network_facets.yml | 0 4 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/Render_diff_upgrade_report.yml delete mode 100644 .github/workflows/Upgrade_network_facets.yml diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index e69de29bb..ae8810709 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -0,0 +1,71 @@ +name: Bytecode Diff Report +env: + BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }} + FACET_SOURCE_PATH: ../../contracts/src + REPORT_OUT_DIR: ${{ vars.REPORT_OUT_DIR }} + BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} + BASESCAN_SEPOLIA_API_KEY: ${{ secrets.BASESCAN_SEPOLIA_API_KEY }} + BASESCAN_SEPOLIA_URL: 'https://api-sepolia.basescan.org/api' + BLOCKSCOUT_SEPOLIA_URL: 'https://base-sepolia.blockscout.com/api' + BLOCKSCOUT_SEPOLIA_API_KEY: ${{ secrets.BLOCKSCOUT_BASE_SEPOLIA_API_KEY }} + GOWORK: off +on: + workflow_dispatch: + inputs: + origin_environment: + description: 'Origin environment' + required: true + type: choice + options: + - 'alpha' + - 'gamma' + - 'omega' + default: 'alpha' + target_environment: + description: 'Target environment' + required: true + type: choice + options: + - 'alpha' + - 'gamma' + - 'omega' + default: 'gamma' + + workflow_call: + inputs: + origin_environment: + description: 'Origin environment' + required: true + type: string + target_environment: + description: 'Target environment' + required: true + type: string + +jobs: + run: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + + - name: Run report generation + run: | + go run main.go -v ${{ inputs.origin_environment }} ${{ inputs.target_environment }} + working-directory: scripts/bytecode-diff + + - name: Get latest facet diff file + run: | + FACET_DIFF_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "facet_diff*.yaml" | sort -r | head -n 1) + echo "Latest facet diff file: $FACET_DIFF_FILE" + echo "FACET_DIFF_FILE=$FACET_DIFF_FILE" >> $GITHUB_ENV + working-directory: scripts/bytecode-diff + + - name: Run facet upgrades based on diffs + run: | + ./scripts/upgrade-facets.sh ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }} ${{ env.FACET_DIFF_FILE }} + working-directory: scripts/bytecode-diff \ No newline at end of file diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index 2f35d1695..a649d5d5c 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -4,7 +4,8 @@ on: workflow_dispatch: jobs: - Run_Bytecode_Diff_Upgrade: + Run_Gamma_Bytecode_Diff_Upgrade: + name: Run Gamma Bytecode Diff Upgrade runs-on: ubuntu-latest steps: - name: Checkout code @@ -12,13 +13,13 @@ jobs: with: ref: main - - name: Gamma Network Diff Report + - name: Network Diff Report uses: ./.github/workflows/Bytecode_diff_report.yml with: origin_environment: alpha target_environment: gamma - - name: Upgrade Gamma Facets - uses: ./.github/workflows/Upgrade_network_facets.yml + - name: Render Network Diff Upgrade Report + uses: ./.github/workflows/Render_diff_upgrade_report.yml with: environment_name: gamma diff --git a/.github/workflows/Render_diff_upgrade_report.yml b/.github/workflows/Render_diff_upgrade_report.yml new file mode 100644 index 000000000..4a20344cc --- /dev/null +++ b/.github/workflows/Render_diff_upgrade_report.yml @@ -0,0 +1,79 @@ +name: Render Diff Upgrade Report +env: + BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }} + REPORT_OUT_DIR: ${{ vars.REPORT_OUT_DIR }} + AWS_ACCESS_KEY_ID: ${{ secrets.RIVER_S3_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.RIVER_S3_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.RIVER_S3_AWS_REGION }} + GOWORK: off + +on: + workflow_dispatch: + inputs: + environment_name: + description: 'Environment name' + required: true + type: choice + options: + - 'alpha' + - 'gamma' + - 'omega' + + workflow_call: + inputs: + environment_name: + description: 'Environment name' + required: true + type: string + +jobs: + Render_Diff_Upgrade_Report: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest facet diff file + run: | + FACET_DIFF_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "facet_diff*.yaml" | sort -r | head -n 1) + echo "Latest facet diff file: $FACET_DIFF_FILE" + echo "FACET_DIFF_FILE=$FACET_DIFF_FILE" >> $GITHUB_ENV + + - name: Run final html report generation w/ updated hashes + id: gen_html_report + run: | + go run main.go add-hashes ${{ inputs.environment_name }} ${{ env.FACET_DIFF_FILE }} + working-directory: scripts/bytecode-diff + + - name: Configure AWS credentials + if: steps.gen_html_report.outcome == 'success' + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Copy HTML report to S3 + if: steps.gen_html_report.outcome == 'success' + run: | + # Find the latest hashed HTML file + HTML_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "*_hashed_*.html" | sort -r | head -n 1) + + if [ -z "$HTML_FILE" ]; then + echo "Error: No hashed HTML file found" + exit 1 + fi + + echo "Latest hashed HTML file: $HTML_FILE" + + # Copy to report.html + aws s3 cp $HTML_FILE s3://${{ inputs.environment_name }}-reports/report.html + + # Copy to report_YYYYMMDD.html + DATED_FILENAME="report_$(date -u +%Y%m%d).html" + aws s3 cp $HTML_FILE s3://${{ inputs.environment_name }}-reports/$DATED_FILENAME + + echo "HTML report copied to S3 bucket ${{ inputs.environment_name }}-reports files report.html and $DATED_FILENAME" diff --git a/.github/workflows/Upgrade_network_facets.yml b/.github/workflows/Upgrade_network_facets.yml deleted file mode 100644 index e69de29bb..000000000 From dd9d63e5dc95cc810e3f6c058b3d1921b6505ad5 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sat, 7 Sep 2024 16:38:00 -0700 Subject: [PATCH 07/30] prettier fix --- .github/workflows/Bytecode_diff_report.yml | 17 ++++++++++++++--- .github/workflows/Network_diff_upgrade.yml | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index ae8810709..f61857b8d 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -31,7 +31,7 @@ on: - 'gamma' - 'omega' default: 'gamma' - + workflow_call: inputs: origin_environment: @@ -52,7 +52,18 @@ jobs: uses: actions/checkout@v4 with: ref: main - + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' # Specify the Go version you need + + - name: Install dependencies + run: | + cd scripts/bytecode-diff + go mod download + go mod tidy + - name: Run report generation run: | go run main.go -v ${{ inputs.origin_environment }} ${{ inputs.target_environment }} @@ -68,4 +79,4 @@ jobs: - name: Run facet upgrades based on diffs run: | ./scripts/upgrade-facets.sh ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }} ${{ env.FACET_DIFF_FILE }} - working-directory: scripts/bytecode-diff \ No newline at end of file + working-directory: scripts/bytecode-diff diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index a649d5d5c..ca3c08058 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -19,7 +19,7 @@ jobs: origin_environment: alpha target_environment: gamma - - name: Render Network Diff Upgrade Report + - name: Render Network Diff Upgrade Report uses: ./.github/workflows/Render_diff_upgrade_report.yml with: environment_name: gamma From 74f1a592dc6229dda6c026b206d54c59002abfbc Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sat, 7 Sep 2024 16:50:13 -0700 Subject: [PATCH 08/30] add go setup --- .github/workflows/Bytecode_diff_report.yml | 6 ++++-- .github/workflows/Network_diff_upgrade.yml | 2 +- .github/workflows/Render_diff_upgrade_report.yml | 14 +++++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index f61857b8d..9199a6a5b 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -1,5 +1,7 @@ name: Bytecode Diff Report env: + FOUNDRY_VERSION: nightly + TESTNET_PRIVATE_KEY: ${{ secrets.GAMMA_BASE_SEPOLIA_DEPLOYER_PK }} BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }} FACET_SOURCE_PATH: ../../contracts/src @@ -51,7 +53,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: main + ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'main' }} - name: Set up Go uses: actions/setup-go@v4 @@ -60,9 +62,9 @@ jobs: - name: Install dependencies run: | - cd scripts/bytecode-diff go mod download go mod tidy + working-directory: scripts/bytecode-diff - name: Run report generation run: | diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index ca3c08058..d0188b87c 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: main + ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'main' }} - name: Network Diff Report uses: ./.github/workflows/Bytecode_diff_report.yml diff --git a/.github/workflows/Render_diff_upgrade_report.yml b/.github/workflows/Render_diff_upgrade_report.yml index 4a20344cc..fe1351068 100644 --- a/.github/workflows/Render_diff_upgrade_report.yml +++ b/.github/workflows/Render_diff_upgrade_report.yml @@ -30,11 +30,23 @@ on: jobs: Render_Diff_Upgrade_Report: runs-on: ubuntu-latest + steps: - name: Checkout code uses: actions/checkout@v4 with: - ref: main + ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'main' }} + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' # Specify the Go version you need + + - name: Install dependencies + run: | + go mod download + go mod tidy + working-directory: scripts/bytecode-diff - name: Get latest facet diff file run: | From 38d5c1bc63087d0e253cfaf592b89166eaad0e72 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sat, 7 Sep 2024 17:20:24 -0700 Subject: [PATCH 09/30] run workflows with current branch to test --- .github/workflows/Bytecode_diff_report.yml | 2 +- .github/workflows/Render_diff_upgrade_report.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 9199a6a5b..c1d6b1f64 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -53,7 +53,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'main' }} + ref: ${{ github.ref }} - name: Set up Go uses: actions/setup-go@v4 diff --git a/.github/workflows/Render_diff_upgrade_report.yml b/.github/workflows/Render_diff_upgrade_report.yml index fe1351068..33a358186 100644 --- a/.github/workflows/Render_diff_upgrade_report.yml +++ b/.github/workflows/Render_diff_upgrade_report.yml @@ -35,7 +35,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'main' }} + ref: ${{ github.ref }} - name: Set up Go uses: actions/setup-go@v4 From 19ba465142ba0b93291aa0ac545eb1c745c8628e Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sat, 7 Sep 2024 17:22:28 -0700 Subject: [PATCH 10/30] allow for testing on current branch --- .github/workflows/Network_diff_upgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index d0188b87c..e3f4cc042 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'main' }} + ref: ${{ github.ref }} - name: Network Diff Report uses: ./.github/workflows/Bytecode_diff_report.yml From ad052f3503ec0c021fe29b5ff558ca39bb3aa27d Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 12:46:20 -0700 Subject: [PATCH 11/30] combine steps into bytecode diff report to share directory space in gh action vm --- .github/workflows/Bytecode_diff_report.yml | 42 +++++++++++++++++++++- .github/workflows/Network_diff_upgrade.yml | 25 ++++--------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index c1d6b1f64..5fe97ccc0 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -11,7 +11,11 @@ env: BASESCAN_SEPOLIA_URL: 'https://api-sepolia.basescan.org/api' BLOCKSCOUT_SEPOLIA_URL: 'https://base-sepolia.blockscout.com/api' BLOCKSCOUT_SEPOLIA_API_KEY: ${{ secrets.BLOCKSCOUT_BASE_SEPOLIA_API_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.RIVER_S3_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.RIVER_S3_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.RIVER_S3_AWS_REGION }} GOWORK: off + on: workflow_dispatch: inputs: @@ -46,7 +50,7 @@ on: type: string jobs: - run: + Render_Network_Diff_Upgrade_Report: runs-on: ubuntu-latest steps: @@ -82,3 +86,39 @@ jobs: run: | ./scripts/upgrade-facets.sh ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }} ${{ env.FACET_DIFF_FILE }} working-directory: scripts/bytecode-diff + + - name: Run final html report generation w/ updated hashes + id: gen_html_report + run: | + go run main.go add-hashes ${{ inputs.target_environment }} ${{ env.FACET_DIFF_FILE }} + working-directory: scripts/bytecode-diff + + - name: Configure AWS credentials + if: steps.gen_html_report.outcome == 'success' + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Copy HTML report to S3 + if: steps.gen_html_report.outcome == 'success' + run: | + # Find the latest hashed HTML file + HTML_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "*_hashed_*.html" | sort -r | head -n 1) + + if [ -z "$HTML_FILE" ]; then + echo "Error: No HTML file found" + exit 1 + fi + + echo "Latest HTML file: $HTML_FILE" + + # Copy to report.html + aws s3 cp $HTML_FILE s3://${{ inputs.target_environment }}-reports/report.html + + # Copy to report_YYYYMMDD.html + DATED_FILENAME="report_$(date -u +%Y%m%d).html" + aws s3 cp $HTML_FILE s3://${{ inputs.target_environment }}-reports/$DATED_FILENAME + + echo "HTML report copied to S3 bucket ${{ inputs.target_environment }}-reports files report.html and $DATED_FILENAME" diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index e3f4cc042..9479d7155 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -4,22 +4,9 @@ on: workflow_dispatch: jobs: - Run_Gamma_Bytecode_Diff_Upgrade: - name: Run Gamma Bytecode Diff Upgrade - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - - name: Network Diff Report - uses: ./.github/workflows/Bytecode_diff_report.yml - with: - origin_environment: alpha - target_environment: gamma - - - name: Render Network Diff Upgrade Report - uses: ./.github/workflows/Render_diff_upgrade_report.yml - with: - environment_name: gamma + Run_Alpha_to_Gamma: + name: Run Alpha to Gamma Diff Upgrade + uses: ./.github/workflows/Bytecode_diff_report.yml + with: + origin_environment: alpha + target_environment: gamma From 2908ad11c14207b2d2ab78090a458fad943d996f Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 12:59:25 -0700 Subject: [PATCH 12/30] add env vars needed directory to each step --- .github/workflows/Bytecode_diff_report.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 5fe97ccc0..c287bcb24 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -71,6 +71,12 @@ jobs: working-directory: scripts/bytecode-diff - name: Run report generation + env: + REPORT_OUT_DIR: ${{ env.REPORT_OUT_DIR }} + BASE_RPC_URL: ${{ env.BASE_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} + FACET_SOURCE_PATH: ${{ env.FACET_SOURCE_PATH }} + GOWORK: ${{ env.GOWORK }} run: | go run main.go -v ${{ inputs.origin_environment }} ${{ inputs.target_environment }} working-directory: scripts/bytecode-diff @@ -83,12 +89,23 @@ jobs: working-directory: scripts/bytecode-diff - name: Run facet upgrades based on diffs + env: + BASE_RPC_URL: ${{ env.BASE_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} + BLOCKSCOUT_SEPOLIA_URL: ${{ env.BLOCKSCOUT_SEPOLIA_URL }} + BLOCKSCOUT_SEPOLIA_API_KEY: ${{ env.BLOCKSCOUT_SEPOLIA_API_KEY }} + BASESCAN_SEPOLIA_API_KEY: ${{ env.BASESCAN_SEPOLIA_API_KEY }} + BASESCAN_SEPOLIA_URL: ${{ env.BASESCAN_SEPOLIA_URL }} run: | ./scripts/upgrade-facets.sh ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }} ${{ env.FACET_DIFF_FILE }} working-directory: scripts/bytecode-diff - name: Run final html report generation w/ updated hashes id: gen_html_report + env: + BASE_RPC_URL: ${{ env.BASE_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} + GOWORK: ${{ env.GOWORK }} run: | go run main.go add-hashes ${{ inputs.target_environment }} ${{ env.FACET_DIFF_FILE }} working-directory: scripts/bytecode-diff From 5335643bca643992b363dd1783f18ba31cd94a55 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 13:11:39 -0700 Subject: [PATCH 13/30] move env declaration --- .github/workflows/Bytecode_diff_report.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index c287bcb24..0562fd542 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -71,15 +71,15 @@ jobs: working-directory: scripts/bytecode-diff - name: Run report generation + run: | + go run main.go -v ${{ inputs.origin_environment }} ${{ inputs.target_environment }} + working-directory: scripts/bytecode-diff env: REPORT_OUT_DIR: ${{ env.REPORT_OUT_DIR }} BASE_RPC_URL: ${{ env.BASE_RPC_URL }} BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} FACET_SOURCE_PATH: ${{ env.FACET_SOURCE_PATH }} GOWORK: ${{ env.GOWORK }} - run: | - go run main.go -v ${{ inputs.origin_environment }} ${{ inputs.target_environment }} - working-directory: scripts/bytecode-diff - name: Get latest facet diff file run: | From fc321e508d8962381770eef397a4c93056353051 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 13:25:45 -0700 Subject: [PATCH 14/30] fix how secrets are passed to workflow call --- .github/workflows/Bytecode_diff_report.yml | 19 +++++++++++++++++++ .github/workflows/Network_diff_upgrade.yml | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 0562fd542..17a127056 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -48,6 +48,25 @@ on: description: 'Target environment' required: true type: string + secrets: + BASE_RPC_URL: + required: true + BASE_SEPOLIA_RPC_URL: + required: true + BASESCAN_API_KEY: + required: true + BASESCAN_SEPOLIA_API_KEY: + required: true + BLOCKSCOUT_BASE_SEPOLIA_API_KEY: + required: true + GAMMA_BASE_SEPOLIA_DEPLOYER_PK: + required: true + RIVER_S3_AWS_ACCESS_KEY_ID: + required: true + RIVER_S3_AWS_SECRET_ACCESS_KEY: + required: true + RIVER_S3_AWS_REGION: + required: true jobs: Render_Network_Diff_Upgrade_Report: diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index 9479d7155..13aa43f80 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -10,3 +10,13 @@ jobs: with: origin_environment: alpha target_environment: gamma + secrets: + BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} + BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }} + BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} + BASESCAN_SEPOLIA_API_KEY: ${{ secrets.BASESCAN_SEPOLIA_API_KEY }} + BLOCKSCOUT_BASE_SEPOLIA_API_KEY: ${{ secrets.BLOCKSCOUT_BASE_SEPOLIA_API_KEY }} + GAMMA_BASE_SEPOLIA_DEPLOYER_PK: ${{ secrets.GAMMA_BASE_SEPOLIA_DEPLOYER_PK }} + RIVER_S3_AWS_ACCESS_KEY_ID: ${{ secrets.RIVER_S3_AWS_ACCESS_KEY_ID }} + RIVER_S3_AWS_SECRET_ACCESS_KEY: ${{ secrets.RIVER_S3_AWS_SECRET_ACCESS_KEY }} + RIVER_S3_AWS_REGION: ${{ secrets.RIVER_S3_AWS_REGION }} From dc50c68d87258a6ca54a6256e9cd7d33719242de Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 13:32:19 -0700 Subject: [PATCH 15/30] add ellided basescal url for debugging --- scripts/bytecode-diff/utils/ethereum.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/bytecode-diff/utils/ethereum.go b/scripts/bytecode-diff/utils/ethereum.go index c83e850ba..4e5b772c5 100644 --- a/scripts/bytecode-diff/utils/ethereum.go +++ b/scripts/bytecode-diff/utils/ethereum.go @@ -92,7 +92,11 @@ func ReadAllFacets(client *ethclient.Client, contractAddress string, basescanAPI // read contract name from basescan source code api contractName, err := GetContractNameFromBasescan(basescanUrl, facet.FacetAddress.Hex(), basescanAPIKey) if err != nil { - return nil, fmt.Errorf("failed to get contract name from Basescan: %w", err) + return nil, fmt.Errorf( + "failed to get contract name from Basescan (%s...): %w", + basescanUrl[:len(basescanUrl)-5], + err, + ) } facets[i].ContractName = contractName From 8762fd76a07207cf5122d7e12d1a615815294a35 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 13:37:20 -0700 Subject: [PATCH 16/30] add missing basescan api key to step env --- .github/workflows/Bytecode_diff_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 17a127056..040f178c8 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -95,6 +95,7 @@ jobs: working-directory: scripts/bytecode-diff env: REPORT_OUT_DIR: ${{ env.REPORT_OUT_DIR }} + BASESCAN_API_KEY: ${{ env.BASESCAN_API_KEY }} BASE_RPC_URL: ${{ env.BASE_RPC_URL }} BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} FACET_SOURCE_PATH: ${{ env.FACET_SOURCE_PATH }} From caf96b256135594bf9be265d205d483427213498 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 13:50:04 -0700 Subject: [PATCH 17/30] add http error handling and logging --- scripts/bytecode-diff/cmd/root.go | 6 ++++++ scripts/bytecode-diff/utils/ethereum.go | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/bytecode-diff/cmd/root.go b/scripts/bytecode-diff/cmd/root.go index cc4380db6..03d92d94a 100644 --- a/scripts/bytecode-diff/cmd/root.go +++ b/scripts/bytecode-diff/cmd/root.go @@ -285,6 +285,12 @@ func executeEnvrionmentDiff( originFacets := make(map[string][]utils.Facet) for diamondName, diamondAddress := range originDiamonds { + if verbose { + log.Info(). + Str("diamondName", fmt.Sprintf("%s", diamondName)). + Str("diamondAddress", diamondAddress). + Msg("Origin Diamond Address") + } facets, err := utils.ReadAllFacets(clients[originEnvironment], diamondAddress, baseConfig.BasescanAPIKey) if err != nil { log.Error().Err(err).Msgf("Error reading all facets for origin diamond %s", diamondName) diff --git a/scripts/bytecode-diff/utils/ethereum.go b/scripts/bytecode-diff/utils/ethereum.go index 4e5b772c5..41d762a7c 100644 --- a/scripts/bytecode-diff/utils/ethereum.go +++ b/scripts/bytecode-diff/utils/ethereum.go @@ -19,7 +19,7 @@ import ( type Facet struct { FacetAddress common.Address Selectors [][4]byte `json:",omitempty"` - SelectorsHex []string ` abi:"-"` + SelectorsHex []string `abi:"-"` ContractName string `json:",omitempty"` BytecodeHash string `json:",omitempty"` } @@ -132,7 +132,10 @@ func ReadAllFacets(client *ethclient.Client, contractAddress string, basescanAPI } func CreateEthereumClients( - baseRpcUrl, baseSepoliaRpcUrl, originEnvironment, targetEnvironment string, + baseRpcUrl string, + baseSepoliaRpcUrl string, + originEnvironment string, + targetEnvironment string, verbose bool, ) (map[string]*ethclient.Client, error) { clients := make(map[string]*ethclient.Client) @@ -187,6 +190,10 @@ func GetContractNameFromBasescan(baseURL, address, apiKey string) (string, error } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("Basescan API returned non-200 status code: %d", resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response body: %w", err) From 55037639e1dcbb4940a2fd7185f1a4d071ca36ae Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 13:55:41 -0700 Subject: [PATCH 18/30] print raw json response --- scripts/bytecode-diff/utils/ethereum.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/bytecode-diff/utils/ethereum.go b/scripts/bytecode-diff/utils/ethereum.go index 41d762a7c..3486bc2de 100644 --- a/scripts/bytecode-diff/utils/ethereum.go +++ b/scripts/bytecode-diff/utils/ethereum.go @@ -199,6 +199,9 @@ func GetContractNameFromBasescan(baseURL, address, apiKey string) (string, error return "", fmt.Errorf("failed to read response body: %w", err) } + // Print the raw JSON response + Log.Info().Msgf("Raw Basescan JSON response: %s", string(body)) + var result struct { Status string `json:"status"` Message string `json:"message"` From f3123e6c0726e1b7aabf4659ad53d04c9575a700 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 13:59:03 -0700 Subject: [PATCH 19/30] throttle calls to basescan to avoid rate limit --- scripts/bytecode-diff/utils/ethereum.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/bytecode-diff/utils/ethereum.go b/scripts/bytecode-diff/utils/ethereum.go index 3486bc2de..6532a83e2 100644 --- a/scripts/bytecode-diff/utils/ethereum.go +++ b/scripts/bytecode-diff/utils/ethereum.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "strings" + "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -89,6 +90,9 @@ func ReadAllFacets(client *ethclient.Client, contractAddress string, basescanAPI } for i, facet := range facets { + // Throttle API calls to 2 per second to avoid being rate limited + time.Sleep(500 * time.Millisecond) + // read contract name from basescan source code api contractName, err := GetContractNameFromBasescan(basescanUrl, facet.FacetAddress.Hex(), basescanAPIKey) if err != nil { From 275ea667191824a03aa7741b02b3affba938fa04 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 14:11:07 -0700 Subject: [PATCH 20/30] fix upgrade script filename path --- .github/workflows/Bytecode_diff_report.yml | 2 +- scripts/bytecode-diff/cmd/run_util.go | 1 + scripts/bytecode-diff/utils/ethereum.go | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 040f178c8..1b635cbe3 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -103,7 +103,7 @@ jobs: - name: Get latest facet diff file run: | - FACET_DIFF_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "facet_diff*.yaml" | sort -r | head -n 1) + FACET_DIFF_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "facet_diff*.yaml" | sort -r | head -n 1 | xargs basename) echo "Latest facet diff file: $FACET_DIFF_FILE" echo "FACET_DIFF_FILE=$FACET_DIFF_FILE" >> $GITHUB_ENV working-directory: scripts/bytecode-diff diff --git a/scripts/bytecode-diff/cmd/run_util.go b/scripts/bytecode-diff/cmd/run_util.go index 7c1ea0918..cb6211048 100644 --- a/scripts/bytecode-diff/cmd/run_util.go +++ b/scripts/bytecode-diff/cmd/run_util.go @@ -45,6 +45,7 @@ var AddHashesCmd = &cobra.Command{ Msg("Environment not supported. Environment can be one of alpha, gamma, or omega.") } + // todo: just require 1 rpc url based on env if baseRpcUrl == "" { log.Fatal(). Msg("Base RPC URL not provided. Set it using --base-rpc-url flag or BASE_RPC_URL environment variable") diff --git a/scripts/bytecode-diff/utils/ethereum.go b/scripts/bytecode-diff/utils/ethereum.go index 6532a83e2..225cd5e23 100644 --- a/scripts/bytecode-diff/utils/ethereum.go +++ b/scripts/bytecode-diff/utils/ethereum.go @@ -97,8 +97,7 @@ func ReadAllFacets(client *ethclient.Client, contractAddress string, basescanAPI contractName, err := GetContractNameFromBasescan(basescanUrl, facet.FacetAddress.Hex(), basescanAPIKey) if err != nil { return nil, fmt.Errorf( - "failed to get contract name from Basescan (%s...): %w", - basescanUrl[:len(basescanUrl)-5], + "failed to get contract name from Basescan: %w", err, ) } @@ -203,8 +202,7 @@ func GetContractNameFromBasescan(baseURL, address, apiKey string) (string, error return "", fmt.Errorf("failed to read response body: %w", err) } - // Print the raw JSON response - Log.Info().Msgf("Raw Basescan JSON response: %s", string(body)) + Log.Debug().Msgf("Raw Basescan JSON response: %s", string(body)) var result struct { Status string `json:"status"` From 48b381d40db85d3251d1b652d400ff174e72d2f3 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 14:16:45 -0700 Subject: [PATCH 21/30] add foundry deps to gh action --- .github/workflows/Bytecode_diff_report.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 1b635cbe3..0159a73c1 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -78,6 +78,19 @@ jobs: with: ref: ${{ github.ref }} + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: ${{ env.FOUNDRY_VERSION }} + + - name: Install node dependencies + run: yarn install --immutable + - name: Set up Go uses: actions/setup-go@v4 with: From 957632fe2ba80a9eae28222c7795cf943dd4efc9 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 14:27:39 -0700 Subject: [PATCH 22/30] use basescan_api_key --- .github/workflows/Bytecode_diff_report.yml | 5 +---- .github/workflows/Network_diff_upgrade.yml | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 0159a73c1..8228e7cdb 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -7,7 +7,6 @@ env: FACET_SOURCE_PATH: ../../contracts/src REPORT_OUT_DIR: ${{ vars.REPORT_OUT_DIR }} BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} - BASESCAN_SEPOLIA_API_KEY: ${{ secrets.BASESCAN_SEPOLIA_API_KEY }} BASESCAN_SEPOLIA_URL: 'https://api-sepolia.basescan.org/api' BLOCKSCOUT_SEPOLIA_URL: 'https://base-sepolia.blockscout.com/api' BLOCKSCOUT_SEPOLIA_API_KEY: ${{ secrets.BLOCKSCOUT_BASE_SEPOLIA_API_KEY }} @@ -55,8 +54,6 @@ on: required: true BASESCAN_API_KEY: required: true - BASESCAN_SEPOLIA_API_KEY: - required: true BLOCKSCOUT_BASE_SEPOLIA_API_KEY: required: true GAMMA_BASE_SEPOLIA_DEPLOYER_PK: @@ -127,7 +124,7 @@ jobs: BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} BLOCKSCOUT_SEPOLIA_URL: ${{ env.BLOCKSCOUT_SEPOLIA_URL }} BLOCKSCOUT_SEPOLIA_API_KEY: ${{ env.BLOCKSCOUT_SEPOLIA_API_KEY }} - BASESCAN_SEPOLIA_API_KEY: ${{ env.BASESCAN_SEPOLIA_API_KEY }} + BASESCAN_API_KEY: ${{ env.BASESCAN_API_KEY }} BASESCAN_SEPOLIA_URL: ${{ env.BASESCAN_SEPOLIA_URL }} run: | ./scripts/upgrade-facets.sh ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }} ${{ env.FACET_DIFF_FILE }} diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index 13aa43f80..c58aee7d2 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -14,7 +14,6 @@ jobs: BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }} BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} - BASESCAN_SEPOLIA_API_KEY: ${{ secrets.BASESCAN_SEPOLIA_API_KEY }} BLOCKSCOUT_BASE_SEPOLIA_API_KEY: ${{ secrets.BLOCKSCOUT_BASE_SEPOLIA_API_KEY }} GAMMA_BASE_SEPOLIA_DEPLOYER_PK: ${{ secrets.GAMMA_BASE_SEPOLIA_DEPLOYER_PK }} RIVER_S3_AWS_ACCESS_KEY_ID: ${{ secrets.RIVER_S3_AWS_ACCESS_KEY_ID }} From 240c47f6844ffb3520125506c7aa98330c7d106e Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 15:08:08 -0700 Subject: [PATCH 23/30] add testnet private key to upgrade env --- .github/workflows/Bytecode_diff_report.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index 8228e7cdb..f3fdbf713 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -119,6 +119,9 @@ jobs: working-directory: scripts/bytecode-diff - name: Run facet upgrades based on diffs + run: | + ./scripts/upgrade-facets.sh ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }} ${{ env.FACET_DIFF_FILE }} + working-directory: scripts/bytecode-diff env: BASE_RPC_URL: ${{ env.BASE_RPC_URL }} BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} @@ -126,19 +129,18 @@ jobs: BLOCKSCOUT_SEPOLIA_API_KEY: ${{ env.BLOCKSCOUT_SEPOLIA_API_KEY }} BASESCAN_API_KEY: ${{ env.BASESCAN_API_KEY }} BASESCAN_SEPOLIA_URL: ${{ env.BASESCAN_SEPOLIA_URL }} - run: | - ./scripts/upgrade-facets.sh ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }} ${{ env.FACET_DIFF_FILE }} - working-directory: scripts/bytecode-diff + TESTNET_PRIVATE_KEY: ${{ env.TESTNET_PRIVATE_KEY }} - name: Run final html report generation w/ updated hashes id: gen_html_report + + run: | + go run main.go add-hashes ${{ inputs.target_environment }} ${{ env.REPORT_OUT_DIR }}/${{ env.FACET_DIFF_FILE }} + working-directory: scripts/bytecode-diff env: BASE_RPC_URL: ${{ env.BASE_RPC_URL }} BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} GOWORK: ${{ env.GOWORK }} - run: | - go run main.go add-hashes ${{ inputs.target_environment }} ${{ env.FACET_DIFF_FILE }} - working-directory: scripts/bytecode-diff - name: Configure AWS credentials if: steps.gen_html_report.outcome == 'success' From aa89146219cb89dd077f4ac04f4032bfe55766ff Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 15:16:02 -0700 Subject: [PATCH 24/30] remove pipefail short circuit from script --- scripts/bytecode-diff/scripts/upgrade-facets.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/bytecode-diff/scripts/upgrade-facets.sh b/scripts/bytecode-diff/scripts/upgrade-facets.sh index 64be1b1b2..f523a5902 100755 --- a/scripts/bytecode-diff/scripts/upgrade-facets.sh +++ b/scripts/bytecode-diff/scripts/upgrade-facets.sh @@ -1,5 +1,4 @@ #!/bin/bash -set -ueo pipefail cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" cd .. From 5577ba4c459aae4607e2a1d6ed3a070c695e86ba Mon Sep 17 00:00:00 2001 From: John Terzis Date: Sun, 8 Sep 2024 15:28:09 -0700 Subject: [PATCH 25/30] add right workign dir --- .github/workflows/Bytecode_diff_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Bytecode_diff_report.yml b/.github/workflows/Bytecode_diff_report.yml index f3fdbf713..12b6bf124 100644 --- a/.github/workflows/Bytecode_diff_report.yml +++ b/.github/workflows/Bytecode_diff_report.yml @@ -171,3 +171,4 @@ jobs: aws s3 cp $HTML_FILE s3://${{ inputs.target_environment }}-reports/$DATED_FILENAME echo "HTML report copied to S3 bucket ${{ inputs.target_environment }}-reports files report.html and $DATED_FILENAME" + working-directory: scripts/bytecode-diff From f6929c45caf53aca84bb77a386e019c64ee2f125 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Mon, 9 Sep 2024 12:58:49 -0700 Subject: [PATCH 26/30] run_util cmd, split out into root and run_util command in main.go, and report.html template --- .prettierignore | 1 + scripts/bytecode-diff/README.md | 22 ++ scripts/bytecode-diff/cmd/root.go | 370 ++++++++++++++++++++ scripts/bytecode-diff/cmd/run_util.go | 175 +++++++++ scripts/bytecode-diff/go.mod | 1 + scripts/bytecode-diff/main.go | 356 +------------------ scripts/bytecode-diff/templates/report.html | 118 +++++++ scripts/bytecode-diff/utils/ethereum.go | 45 ++- 8 files changed, 724 insertions(+), 364 deletions(-) create mode 100644 scripts/bytecode-diff/cmd/root.go create mode 100644 scripts/bytecode-diff/cmd/run_util.go create mode 100644 scripts/bytecode-diff/templates/report.html diff --git a/.prettierignore b/.prettierignore index 38a630a2a..b13b94b60 100644 --- a/.prettierignore +++ b/.prettierignore @@ -47,4 +47,5 @@ yarn.lock packages/generated/* +scripts/bytecode-diff/templates/* packages/proto/src/protocol_*.ts diff --git a/scripts/bytecode-diff/README.md b/scripts/bytecode-diff/README.md index 245f1a888..0f18dd67c 100644 --- a/scripts/bytecode-diff/README.md +++ b/scripts/bytecode-diff/README.md @@ -83,6 +83,28 @@ diamonds: ``` +### Run keccak256 hash generation on deployed contracts + +```bash +GOWORK=off go run main.go add-hashes gamma deployed-diffs/facet_diff_090624_1.yaml + +# output to new yaml file suffixed with _hashed.yaml including bytecodeHash for each contract in deployments section +➜ bytecode-diff git:(jt/net-62-upgrade-script-2) ✗ yq e '.deployments' deployed-diffs/facet_diff_090624_1_hashed.yaml +Architect: + address: 0xa18a3df4f63cdcae943d9c76730adf2812388de4 + baseScanLink: https://sepolia.basescan.org/tx/0x4280ef1300fe001e7d85e7495eba13fc99be53ee7a7060e753d466f8bebf1622 + bytecodeHash: 0x20d0a86e9ea31a39663285aacfe88705983520a4482a7bac5ada891c9adfe090 + deploymentDate: 2024-09-06 19:04 + transactionHash: 0x4280ef1300fe001e7d85e7495eba13fc99be53ee7a7060e753d466f8bebf1622 +Banning: + address: 0x4d88d1fbba6ce6bcdb4381549ee0b7c0d2b56919 + baseScanLink: https://sepolia.basescan.org/tx/0x4ccbaf9750bcd0971975e73a24b05f1c51d4703cf72a406356c79eb54de9c33c + bytecodeHash: 0xa2ce3e77ba060ff1d59ed384e1c6c5788f308ad8bbbef612eb3e5de4e1d79de8 + deploymentDate: 2024-09-06 19:05 + transactionHash: 0x4ccbaf9750bcd0971975e73a24b05f1c51d4703cf72a406356c79eb54de9c33c +... +``` + ### Flags ```bash diff --git a/scripts/bytecode-diff/cmd/root.go b/scripts/bytecode-diff/cmd/root.go new file mode 100644 index 000000000..03d92d94a --- /dev/null +++ b/scripts/bytecode-diff/cmd/root.go @@ -0,0 +1,370 @@ +package cmd + +import ( + "bytecode-diff/utils" + "fmt" + "os" + "path/filepath" + + "github.com/joho/godotenv" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +var ( + supportedEnvironments = []string{"alpha", "gamma", "omega"} + baseRpcUrl string + facetSourcePath string + compiledFacetsPath string + sourceDiffDir string + sourceDiff bool + reportOutDir string + originEnvironment string + targetEnvironment string + deploymentsPath string + baseSepoliaRpcUrl string + logLevel string +) + +func init() { + log := zerolog.New(os.Stderr).With().Timestamp().Logger() + utils.SetLogger(log) + + rootCmd.PersistentFlags(). + StringVar(&logLevel, "log-level", "info", "Set the logging level (debug, info, warn, error)") + rootCmd.Flags().StringVarP(&baseRpcUrl, "base-rpc", "b", "", "Base RPC provider URL") + rootCmd.Flags().StringVarP(&baseSepoliaRpcUrl, "base-sepolia-rpc", "", "", "Base Sepolia RPC provider URL") + rootCmd.Flags().BoolVarP(&sourceDiff, "source-diff-only", "s", false, "Run source code diff") + rootCmd.Flags().StringVar(&sourceDiffDir, "source-diff-log", "source-diffs", "Path to diff log file") + rootCmd.Flags().StringVar(&compiledFacetsPath, "compiled-facets", "../../contracts/out", "Path to compiled facets") + rootCmd.Flags().StringVar(&facetSourcePath, "facets", "", "Path to facet source files") + rootCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output") + rootCmd.Flags().StringVar(&reportOutDir, "report-out-dir", "deployed-diffs", "Path to report output directory") + rootCmd.Flags(). + StringVar(&deploymentsPath, "deployments", "../../contracts/deployments", "Path to deployments directory") + + rootCmd.AddCommand(AddHashesCmd) +} + +func setLogLevel(level string) { + switch level { + case "debug": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "info": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "warn": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "error": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + default: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } +} + +func Execute() { + if err := godotenv.Load(); err != nil { + log.Warn().Msg("No .env file found") + } + + if err := rootCmd.Execute(); err != nil { + log.Error().Err(err).Msg("Error executing root command") + os.Exit(1) + } +} + +var rootCmd = &cobra.Command{ + Use: "bytecode-diff [origin_environment] [target_environment]", + Short: "A tool to retrieve and display contract bytecode diff for Base", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + setLogLevel(logLevel) + }, + Args: func(cmd *cobra.Command, args []string) error { + if sourceDiff { + if len(args) != 0 { + return fmt.Errorf("no positional arguments expected when --source-diff-only is set") + } + } else { + if len(args) < 2 { + return fmt.Errorf("at least two arguments required when --source-diff-only is not set, [origin_environment], [target_environment]") + } + } + return nil + }, + PreRun: func(cmd *cobra.Command, args []string) { + if sourceDiff { + envSourceDiffDir := os.Getenv("SOURCE_DIFF_DIR") + if envSourceDiffDir != "" { + sourceDiffDir = envSourceDiffDir + } + + if sourceDiffDir == "" { + sourceDiffDir = cmd.Flag("source-diff-log").Value.String() + } + + facetSourcePath = os.Getenv("FACET_SOURCE_PATH") + if facetSourcePath == "" { + facetSourcePath = cmd.Flag("facets").Value.String() + } + if facetSourcePath == "" { + log.Fatal(). + Msg("Facet source path is missing. Set it using --facets flag or FACET_SOURCE_PATH environment variable") + } + + compiledFacetsPath = os.Getenv("COMPILED_FACETS_PATH") + log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from environment") + if compiledFacetsPath == "" { + compiledFacetsPath = cmd.Flag("compiled-facets").Value.String() + log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from flag") + } + if compiledFacetsPath == "" { + log.Fatal(). + Msg("Compiled facets path is missing. Set it using --compiled-facets flag or COMPILED_FACETS_PATH environment variable") + } + + envReportOutDir := os.Getenv("REPORT_OUT_DIR") + if envReportOutDir != "" { + reportOutDir = envReportOutDir + } + if reportOutDir == "" { + reportOutDir = cmd.Flag("report-out-dir").Value.String() + } + if reportOutDir == "" { + log.Fatal(). + Msg("Report out directory is missing. Set it using --report-out-dir flag or REPORT_OUT_DIR environment variable") + } + return + } + + envDeploymentsPath := os.Getenv("DEPLOYMENTS_PATH") + if envDeploymentsPath != "" { + deploymentsPath = envDeploymentsPath + } + if deploymentsPath == "" { + deploymentsPath = cmd.Flag("deployments").Value.String() + } + if deploymentsPath == "" { + log.Fatal(). + Msg("Deployments path is missing. Set it using --deployments flag or DEPLOYMENTS_PATH environment variable") + } + }, + Run: func(cmd *cobra.Command, args []string) { + verbose, _ := cmd.Flags().GetBool("verbose") + if sourceDiff { + + log.Info(). + Str("facetSourcePath", facetSourcePath). + Str("compiledFacetsPath", compiledFacetsPath). + Msg("Running diff for facet path recursively only compiled facet contracts") + + if err := executeSourceDiff(verbose, facetSourcePath, compiledFacetsPath, sourceDiffDir); err != nil { + log.Fatal().Err(err).Msg("Error executing source diff") + return + } + } else { + + originEnvironment, targetEnvironment = args[0], args[1] + for _, environment := range []string{originEnvironment, targetEnvironment} { + if !utils.Contains(supportedEnvironments, environment) { + log.Fatal().Str("environment", environment).Msg("Environment not supported. Environment can be one of alpha, gamma, or omega.") + } + } + + log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Environment") + + if baseRpcUrl == "" { + baseRpcUrl = os.Getenv("BASE_RPC_URL") + if baseRpcUrl == "" { + log.Fatal().Msg("Base RPC URL not provided. Set it using --base-rpc flag or BASE_RPC_URL environment variable") + } + } + + if baseSepoliaRpcUrl == "" { + baseSepoliaRpcUrl = os.Getenv("BASE_SEPOLIA_RPC_URL") + if baseSepoliaRpcUrl == "" { + log.Fatal().Msg("Base Sepolia RPC URL not provided. Set it using --base-sepolia-rpc flag or BASE_SEPOLIA_RPC_URL environment variable") + } + } + + basescanAPIKey := os.Getenv("BASESCAN_API_KEY") + if basescanAPIKey == "" { + log.Fatal().Msg("BaseScan API key not provided. Set it using BASESCAN_API_KEY environment variable") + } + + log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Running diff for environment") + // Create BaseConfig struct + baseConfig := utils.BaseConfig{ + BaseRpcUrl: baseRpcUrl, + BaseSepoliaRpcUrl: baseSepoliaRpcUrl, + BasescanAPIKey: basescanAPIKey, + } + + if err := executeEnvrionmentDiff(verbose, baseConfig, deploymentsPath, originEnvironment, targetEnvironment, reportOutDir); err != nil { + log.Fatal().Err(err).Msg("Error executing environment diff") + } + } + }, +} + +func executeSourceDiff(verbose bool, facetSourcePath, compiledFacetsPath string, reportOutDir string) error { + facetFiles, err := utils.GetFacetFiles(facetSourcePath) + if err != nil { + log.Error(). + Str("facetSourcePath", facetSourcePath). + Str("compiledFacetsPath", compiledFacetsPath). + Err(err). + Msg("Error getting facet files") + return err + } + log.Debug().Int("facetFilesCount", len(facetFiles)).Msg("Facet files length") + + compiledHashes, err := utils.GetCompiledFacetHashes(compiledFacetsPath, facetFiles) + if err != nil { + log.Error(). + Err(err). + Str("compiledFacetsPath", compiledFacetsPath). + Msg("Error getting compiled facet hashes") + return err + } + + if verbose { + log.Info().Int("compiledHashesCount", len(compiledHashes)).Msg("Compiled Facet Hashes") + for file, hash := range compiledHashes { + log.Info().Str("file", file).Str("hash", hash).Msg("Compiled Facet Hash") + } + } + + err = utils.CreateFacetHashesReport(compiledFacetsPath, compiledHashes, reportOutDir, verbose) + if err != nil { + log.Error().Err(err).Msg("Error creating facet hashes report") + return err + } + + return nil +} + +func executeEnvrionmentDiff( + verbose bool, + baseConfig utils.BaseConfig, + deploymentsPath, originEnvironment, targetEnvironment string, + reportOutDir string, +) error { + // walk environment diamonds and get all facet addresses from DiamondLoupe facet view + baseDiamonds := []utils.Diamond{ + utils.BaseRegistry, + utils.Space, + utils.SpaceFactory, + utils.SpaceOwner, + } + originDeploymentsPath := filepath.Join(deploymentsPath, originEnvironment) + originDiamonds, err := utils.GetDiamondAddresses(originDeploymentsPath, baseDiamonds, verbose) + if err != nil { + log.Error().Err(err).Msgf("Error getting diamond addresses for origin environment %s", originEnvironment) + return err + } + targetDeploymentsPath := filepath.Join(deploymentsPath, targetEnvironment) + targetDiamonds, err := utils.GetDiamondAddresses(targetDeploymentsPath, baseDiamonds, verbose) + if err != nil { + log.Error().Err(err).Msgf("Error getting diamond addresses for target environment %s", targetEnvironment) + return err + } + // Create Ethereum client + clients, err := utils.CreateEthereumClients( + baseConfig.BaseRpcUrl, + baseConfig.BaseSepoliaRpcUrl, + originEnvironment, + targetEnvironment, + verbose, + ) + defer func() { + for _, client := range clients { + client.Close() + } + }() + // getCode for all facet addresses over base rpc url and compare with compiled hashes + originFacets := make(map[string][]utils.Facet) + + for diamondName, diamondAddress := range originDiamonds { + if verbose { + log.Info(). + Str("diamondName", fmt.Sprintf("%s", diamondName)). + Str("diamondAddress", diamondAddress). + Msg("Origin Diamond Address") + } + facets, err := utils.ReadAllFacets(clients[originEnvironment], diamondAddress, baseConfig.BasescanAPIKey) + if err != nil { + log.Error().Err(err).Msgf("Error reading all facets for origin diamond %s", diamondName) + return err + } + err = utils.AddContractCodeHashes(clients[originEnvironment], facets) + if err != nil { + log.Error().Err(err).Msgf("Error adding contract code hashes for origin diamond %s", diamondName) + return err + } + originFacets[string(diamondName)] = facets + } + + targetFacets := make(map[string][]utils.Facet) + for diamondName, diamondAddress := range targetDiamonds { + facets, err := utils.ReadAllFacets(clients[targetEnvironment], diamondAddress, baseConfig.BasescanAPIKey) + if err != nil { + log.Error().Err(err).Msgf("Error reading all facets for target diamond %s", diamondName) + return err + } + err = utils.AddContractCodeHashes(clients[targetEnvironment], facets) + if err != nil { + log.Error().Err(err).Msgf("Error adding contract code hashes for target diamond %s", diamondName) + return err + } + targetFacets[string(diamondName)] = facets + } + if verbose { + for diamondName, facets := range originFacets { + log.Info().Str("diamondName", diamondName).Msg("Origin Facets for Diamond contract") + for _, facet := range facets { + log.Info(). + Str("facetAddress", facet.FacetAddress.Hex()). + Str("contractName", facet.ContractName). + Interface("selectors", facet.SelectorsHex). + Msg("Facet") + } + } + for diamondName, facets := range targetFacets { + log.Info().Str("diamondName", diamondName).Msg("Target Facets for Diamond contract") + for _, facet := range facets { + log.Info(). + Str("facetAddress", facet.FacetAddress.Hex()). + Str("contractName", facet.ContractName). + Interface("selectors", facet.SelectorsHex). + Msg("Facet") + } + } + } + + // compare facets and create report + differences := utils.CompareFacets(originFacets, targetFacets) + if verbose { + for diamondName, facets := range differences { + log.Info().Str("diamondName", diamondName).Msg("Differences for Diamond contract") + for _, facet := range facets { + log.Info(). + Str("facetAddress", facet.OriginContractAddress.Hex()). + Str("originContractName", facet.OriginContractName). + Msg("Origin Facet") + log.Info(). + Interface("selectorDiff", facet.SelectorsDiff). + Msg("Selector Diff") + + } + } + } + + // create report + log.Info().Str("reportOutDir", reportOutDir).Msg("Generating YAML report") + err = utils.GenerateYAMLReport(originEnvironment, targetEnvironment, differences, reportOutDir) + if err != nil { + log.Error().Err(err).Msg("Error generating YAML report") + return err + } + return nil +} diff --git a/scripts/bytecode-diff/cmd/run_util.go b/scripts/bytecode-diff/cmd/run_util.go new file mode 100644 index 000000000..cb6211048 --- /dev/null +++ b/scripts/bytecode-diff/cmd/run_util.go @@ -0,0 +1,175 @@ +package cmd + +import ( + "bytecode-diff/utils" + "fmt" + "html/template" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +var htmlRender bool + +func getIncrementedFileName(basePath string, extension string) string { + dir := filepath.Dir(basePath) + fileName := filepath.Base(basePath[:len(basePath)-len(filepath.Ext(basePath))]) + + for i := 1; ; i++ { + newFileName := fmt.Sprintf("%s_hashed_%d%s", fileName, i, extension) + fullPath := filepath.Join(dir, newFileName) + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + return fullPath + } + } +} + +var AddHashesCmd = &cobra.Command{ + Use: "add-hashes [environment] [yaml_file_path]", + Short: "Add bytecode hashes and render yaml, html reports", + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + environment := args[0] + yamlFilePath := args[1] + + supportedEnvironments := []string{"alpha", "gamma", "omega"} + if !utils.Contains(supportedEnvironments, environment) { + log.Fatal(). + Str("environment", environment). + Msg("Environment not supported. Environment can be one of alpha, gamma, or omega.") + } + + // todo: just require 1 rpc url based on env + if baseRpcUrl == "" { + log.Fatal(). + Msg("Base RPC URL not provided. Set it using --base-rpc-url flag or BASE_RPC_URL environment variable") + } + + if baseSepoliaRpcUrl == "" { + log.Fatal(). + Msg("Base Sepolia RPC URL not provided. Set it using --base-sepolia-rpc-url flag or BASE_SEPOLIA_RPC_URL environment variable") + } + + // Create Ethereum client + clients, err := utils.CreateEthereumClients( + baseRpcUrl, + baseSepoliaRpcUrl, + environment, + "", + false, + ) + if err != nil { + log.Fatal().Err(err).Msg("Failed to create Ethereum client") + } + defer clients[environment].Close() + + // Read YAML file + yamlData, err := os.ReadFile(yamlFilePath) + if err != nil { + log.Fatal().Err(err).Str("file", yamlFilePath).Msg("Failed to read YAML file") + } + + var data map[string]interface{} + err = yaml.Unmarshal(yamlData, &data) + if err != nil { + log.Fatal().Err(err).Msg("Failed to unmarshal YAML data") + } + + // Process deployments + deployments, ok := data["deployments"].(map[string]interface{}) + if !ok { + log.Fatal().Msg("Invalid YAML structure: 'deployments' field not found or not a map") + } + + for name, deployment := range deployments { + deploymentMap, ok := deployment.(map[string]interface{}) + if !ok { + log.Warn().Str("name", name).Msg("Skipping invalid deployment entry") + continue + } + + address, ok := deploymentMap["address"].(string) + if !ok { + log.Warn().Str("name", name).Msg("Skipping deployment without valid address") + continue + } + + addressBytes := common.HexToAddress(address) + hash, err := utils.GetContractCodeHash(clients[environment], addressBytes) + if err != nil { + log.Error().Err(err).Str("name", name).Str("address", address).Msg("Failed to get contract code hash") + continue + } + + deploymentMap["bytecodeHash"] = hash + } + + // Write updated YAML file + outputPath := getIncrementedFileName(yamlFilePath, ".yaml") + + updatedYAML, err := yaml.Marshal(data) + if err != nil { + log.Fatal().Err(err).Msg("Failed to marshal updated YAML data") + } + + err = os.WriteFile(outputPath, updatedYAML, 0644) + if err != nil { + log.Fatal().Err(err).Str("file", outputPath).Msg("Failed to write updated YAML file") + } + + // After writing the updated YAML file + if htmlRender { + htmlContent, err := renderYAMLToHTML(updatedYAML, environment) + if err != nil { + log.Error().Err(err).Msg("Failed to render YAML to HTML") + } else { + htmlOutputPath := getIncrementedFileName(yamlFilePath, ".html") + err = os.WriteFile(htmlOutputPath, []byte(htmlContent), 0644) + if err != nil { + log.Error().Err(err).Str("file", htmlOutputPath).Msg("Failed to write HTML file") + } else { + log.Info().Str("file", htmlOutputPath).Msg("Successfully wrote HTML file with bytecode hashes") + } + } + } + + log.Info().Str("file", outputPath).Msg("Successfully wrote updated YAML file with bytecode hashes") + }, +} + +func renderYAMLToHTML(yamlData []byte, environment string) (string, error) { + var data map[string]interface{} + err := yaml.Unmarshal(yamlData, &data) + if err != nil { + return "", fmt.Errorf("failed to unmarshal YAML data: %w", err) + } + + data["environment"] = environment + data["reportTime"] = time.Now().UTC().Format(time.RFC3339) + + t, err := template.ParseFiles("templates/report.html") + if err != nil { + return "", fmt.Errorf("failed to parse HTML template: %w", err) + } + + var buf strings.Builder + err = t.Execute(&buf, data) + if err != nil { + return "", fmt.Errorf("failed to execute HTML template: %w", err) + } + + return buf.String(), nil +} + +func init() { + AddHashesCmd.Flags().StringVar(&baseRpcUrl, "base-rpc-url", os.Getenv("BASE_RPC_URL"), "Base RPC URL") + AddHashesCmd.Flags(). + StringVar(&baseSepoliaRpcUrl, "base-sepolia-rpc-url", os.Getenv("BASE_SEPOLIA_RPC_URL"), "Base Sepolia RPC URL") + AddHashesCmd.Flags().BoolVar(&htmlRender, "html-render", true, "Render output as HTML") +} diff --git a/scripts/bytecode-diff/go.mod b/scripts/bytecode-diff/go.mod index f5cf6599b..af385086a 100644 --- a/scripts/bytecode-diff/go.mod +++ b/scripts/bytecode-diff/go.mod @@ -8,6 +8,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.1 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( diff --git a/scripts/bytecode-diff/main.go b/scripts/bytecode-diff/main.go index a3619bc30..35d7a803c 100644 --- a/scripts/bytecode-diff/main.go +++ b/scripts/bytecode-diff/main.go @@ -1,359 +1,7 @@ package main -import ( - "bytecode-diff/utils" - "fmt" - "os" - "path/filepath" - - "github.com/joho/godotenv" - "github.com/rs/zerolog" - "github.com/spf13/cobra" -) - -var log zerolog.Logger - -func init() { - log = zerolog.New(os.Stderr).With().Timestamp().Logger() - utils.SetLogger(log) -} - -func setLogLevel(level string) { - switch level { - case "debug": - zerolog.SetGlobalLevel(zerolog.DebugLevel) - case "info": - zerolog.SetGlobalLevel(zerolog.InfoLevel) - case "warn": - zerolog.SetGlobalLevel(zerolog.WarnLevel) - case "error": - zerolog.SetGlobalLevel(zerolog.ErrorLevel) - default: - zerolog.SetGlobalLevel(zerolog.InfoLevel) - } -} +import "bytecode-diff/cmd" func main() { - if err := godotenv.Load(); err != nil { - log.Warn().Msg("No .env file found") - } - - supportedEnvironments := []string{"alpha", "gamma", "omega"} - var baseRpcUrl string - var facetSourcePath string - var compiledFacetsPath string - var sourceDiffDir string - var sourceDiff bool - var reportOutDir string - var originEnvironment, targetEnvironment string - var deploymentsPath string - var baseSepoliaRpcUrl string - var logLevel string - - rootCmd := &cobra.Command{ - Use: "bytecode-diff [origin_environment] [target_environment]", - Short: "A tool to retrieve and display contract bytecode diff for Base", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - Args: func(cmd *cobra.Command, args []string) error { - if sourceDiff { - if len(args) != 0 { - return fmt.Errorf("no positional arguments expected when --source-diff-only is set") - } - } else { - if len(args) < 2 { - return fmt.Errorf("at least two arguments required when --source-diff-only is not set, [origin_environment], [target_environment]") - } - } - return nil - }, - PreRun: func(cmd *cobra.Command, args []string) { - if sourceDiff { - envSourceDiffDir := os.Getenv("SOURCE_DIFF_DIR") - if envSourceDiffDir != "" { - sourceDiffDir = envSourceDiffDir - } - - if sourceDiffDir == "" { - sourceDiffDir = cmd.Flag("source-diff-log").Value.String() - } - - facetSourcePath = os.Getenv("FACET_SOURCE_PATH") - if facetSourcePath == "" { - facetSourcePath = cmd.Flag("facets").Value.String() - } - if facetSourcePath == "" { - log.Fatal(). - Msg("Facet source path is missing. Set it using --facets flag or FACET_SOURCE_PATH environment variable") - } - - compiledFacetsPath = os.Getenv("COMPILED_FACETS_PATH") - log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from environment") - if compiledFacetsPath == "" { - compiledFacetsPath = cmd.Flag("compiled-facets").Value.String() - log.Debug().Str("compiledFacetsPath", compiledFacetsPath).Msg("Compiled facets path from flag") - } - if compiledFacetsPath == "" { - log.Fatal(). - Msg("Compiled facets path is missing. Set it using --compiled-facets flag or COMPILED_FACETS_PATH environment variable") - } - - envReportOutDir := os.Getenv("REPORT_OUT_DIR") - if envReportOutDir != "" { - reportOutDir = envReportOutDir - } - if reportOutDir == "" { - reportOutDir = cmd.Flag("report-out-dir").Value.String() - } - if reportOutDir == "" { - log.Fatal(). - Msg("Report out directory is missing. Set it using --report-out-dir flag or REPORT_OUT_DIR environment variable") - } - return - } - - envDeploymentsPath := os.Getenv("DEPLOYMENTS_PATH") - if envDeploymentsPath != "" { - deploymentsPath = envDeploymentsPath - } - if deploymentsPath == "" { - deploymentsPath = cmd.Flag("deployments").Value.String() - } - if deploymentsPath == "" { - log.Fatal(). - Msg("Deployments path is missing. Set it using --deployments flag or DEPLOYMENTS_PATH environment variable") - } - }, - Run: func(cmd *cobra.Command, args []string) { - verbose, _ := cmd.Flags().GetBool("verbose") - if sourceDiff { - - log.Info(). - Str("facetSourcePath", facetSourcePath). - Str("compiledFacetsPath", compiledFacetsPath). - Msg("Running diff for facet path recursively only compiled facet contracts") - - if err := executeSourceDiff(verbose, facetSourcePath, compiledFacetsPath, sourceDiffDir); err != nil { - log.Fatal().Err(err).Msg("Error executing source diff") - return - } - } else { - - originEnvironment, targetEnvironment = args[0], args[1] - for _, environment := range []string{originEnvironment, targetEnvironment} { - if !utils.Contains(supportedEnvironments, environment) { - log.Fatal().Str("environment", environment).Msg("Environment not supported. Environment can be one of alpha, gamma, or omega.") - } - } - - log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Environment") - - if baseRpcUrl == "" { - baseRpcUrl = os.Getenv("BASE_RPC_URL") - if baseRpcUrl == "" { - log.Fatal().Msg("Base RPC URL not provided. Set it using --base-rpc flag or BASE_RPC_URL environment variable") - } - } - - if baseSepoliaRpcUrl == "" { - baseSepoliaRpcUrl = os.Getenv("BASE_SEPOLIA_RPC_URL") - if baseSepoliaRpcUrl == "" { - log.Fatal().Msg("Base Sepolia RPC URL not provided. Set it using --base-sepolia-rpc flag or BASE_SEPOLIA_RPC_URL environment variable") - } - } - - basescanAPIKey := os.Getenv("BASESCAN_API_KEY") - if basescanAPIKey == "" { - log.Fatal().Msg("BaseScan API key not provided. Set it using BASESCAN_API_KEY environment variable") - } - - log.Info().Str("originEnvironment", originEnvironment).Str("targetEnvironment", targetEnvironment).Msg("Running diff for environment") - // Create BaseConfig struct - baseConfig := utils.BaseConfig{ - BaseRpcUrl: baseRpcUrl, - BaseSepoliaRpcUrl: baseSepoliaRpcUrl, - BasescanAPIKey: basescanAPIKey, - } - - if err := executeEnvrionmentDiff(verbose, baseConfig, deploymentsPath, originEnvironment, targetEnvironment, reportOutDir); err != nil { - log.Fatal().Err(err).Msg("Error executing environment diff") - } - } - }, - } - rootCmd.Flags().StringVarP(&baseRpcUrl, "base-rpc", "b", "", "Base RPC provider URL") - rootCmd.Flags().StringVarP(&baseSepoliaRpcUrl, "base-sepolia-rpc", "", "", "Base Sepolia RPC provider URL") - rootCmd.Flags().BoolVarP(&sourceDiff, "source-diff-only", "s", false, "Run source code diff") - rootCmd.Flags().StringVar(&sourceDiffDir, "source-diff-log", "source-diffs", "Path to diff log file") - rootCmd.Flags().StringVar(&compiledFacetsPath, "compiled-facets", "../../contracts/out", "Path to compiled facets") - rootCmd.Flags().StringVar(&facetSourcePath, "facets", "", "Path to facet source files") - rootCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output") - rootCmd.Flags().StringVar(&reportOutDir, "report-out-dir", "deployed-diffs", "Path to report output directory") - rootCmd.Flags(). - StringVar(&deploymentsPath, "deployments", "../../contracts/deployments", "Path to deployments directory") - rootCmd.PersistentFlags(). - StringVar(&logLevel, "log-level", "info", "Set the logging level (debug, info, warn, error)") - - if err := rootCmd.Execute(); err != nil { - log.Error().Err(err).Msg("Error executing root command") - os.Exit(1) - } -} - -func executeSourceDiff(verbose bool, facetSourcePath, compiledFacetsPath string, reportOutDir string) error { - facetFiles, err := utils.GetFacetFiles(facetSourcePath) - if err != nil { - log.Error(). - Str("facetSourcePath", facetSourcePath). - Str("compiledFacetsPath", compiledFacetsPath). - Err(err). - Msg("Error getting facet files") - return err - } - log.Debug().Int("facetFilesCount", len(facetFiles)).Msg("Facet files length") - - compiledHashes, err := utils.GetCompiledFacetHashes(compiledFacetsPath, facetFiles) - if err != nil { - log.Error(). - Err(err). - Str("compiledFacetsPath", compiledFacetsPath). - Msg("Error getting compiled facet hashes") - return err - } - - if verbose { - log.Info().Int("compiledHashesCount", len(compiledHashes)).Msg("Compiled Facet Hashes") - for file, hash := range compiledHashes { - log.Info().Str("file", file).Str("hash", hash).Msg("Compiled Facet Hash") - } - } - - err = utils.CreateFacetHashesReport(compiledFacetsPath, compiledHashes, reportOutDir, verbose) - if err != nil { - log.Error().Err(err).Msg("Error creating facet hashes report") - return err - } - - return nil -} - -func executeEnvrionmentDiff( - verbose bool, - baseConfig utils.BaseConfig, - deploymentsPath, originEnvironment, targetEnvironment string, - reportOutDir string, -) error { - // walk environment diamonds and get all facet addresses from DiamondLoupe facet view - baseDiamonds := []utils.Diamond{ - utils.BaseRegistry, - utils.Space, - utils.SpaceFactory, - utils.SpaceOwner, - } - originDeploymentsPath := filepath.Join(deploymentsPath, originEnvironment) - originDiamonds, err := utils.GetDiamondAddresses(originDeploymentsPath, baseDiamonds, verbose) - if err != nil { - log.Error().Err(err).Msgf("Error getting diamond addresses for origin environment %s", originEnvironment) - return err - } - targetDeploymentsPath := filepath.Join(deploymentsPath, targetEnvironment) - targetDiamonds, err := utils.GetDiamondAddresses(targetDeploymentsPath, baseDiamonds, verbose) - if err != nil { - log.Error().Err(err).Msgf("Error getting diamond addresses for target environment %s", targetEnvironment) - return err - } - // Create Ethereum client - clients, err := utils.CreateEthereumClients( - baseConfig.BaseRpcUrl, - baseConfig.BaseSepoliaRpcUrl, - originEnvironment, - targetEnvironment, - verbose, - ) - defer func() { - for _, client := range clients { - client.Close() - } - }() - // getCode for all facet addresses over base rpc url and compare with compiled hashes - originFacets := make(map[string][]utils.Facet) - - for diamondName, diamondAddress := range originDiamonds { - facets, err := utils.ReadAllFacets(clients[originEnvironment], diamondAddress, baseConfig.BasescanAPIKey) - if err != nil { - log.Error().Err(err).Msgf("Error reading all facets for origin diamond %s", diamondName) - return err - } - err = utils.AddContractCodeHashes(clients[originEnvironment], facets) - if err != nil { - log.Error().Err(err).Msgf("Error adding contract code hashes for origin diamond %s", diamondName) - return err - } - originFacets[string(diamondName)] = facets - } - - targetFacets := make(map[string][]utils.Facet) - for diamondName, diamondAddress := range targetDiamonds { - facets, err := utils.ReadAllFacets(clients[targetEnvironment], diamondAddress, baseConfig.BasescanAPIKey) - if err != nil { - log.Error().Err(err).Msgf("Error reading all facets for target diamond %s", diamondName) - return err - } - err = utils.AddContractCodeHashes(clients[targetEnvironment], facets) - if err != nil { - log.Error().Err(err).Msgf("Error adding contract code hashes for target diamond %s", diamondName) - return err - } - targetFacets[string(diamondName)] = facets - } - if verbose { - for diamondName, facets := range originFacets { - log.Info().Str("diamondName", diamondName).Msg("Origin Facets for Diamond contract") - for _, facet := range facets { - log.Info(). - Str("facetAddress", facet.FacetAddress.Hex()). - Str("contractName", facet.ContractName). - Interface("selectors", facet.SelectorsHex). - Msg("Facet") - } - } - for diamondName, facets := range targetFacets { - log.Info().Str("diamondName", diamondName).Msg("Target Facets for Diamond contract") - for _, facet := range facets { - log.Info(). - Str("facetAddress", facet.FacetAddress.Hex()). - Str("contractName", facet.ContractName). - Interface("selectors", facet.SelectorsHex). - Msg("Facet") - } - } - } - - // compare facets and create report - differences := utils.CompareFacets(originFacets, targetFacets) - if verbose { - for diamondName, facets := range differences { - log.Info().Str("diamondName", diamondName).Msg("Differences for Diamond contract") - for _, facet := range facets { - log.Info(). - Str("facetAddress", facet.OriginContractAddress.Hex()). - Str("originContractName", facet.OriginContractName). - Msg("Origin Facet") - log.Info(). - Interface("selectorDiff", facet.SelectorsDiff). - Msg("Selector Diff") - - } - } - } - - // create report - log.Info().Str("reportOutDir", reportOutDir).Msg("Generating YAML report") - err = utils.GenerateYAMLReport(originEnvironment, targetEnvironment, differences, reportOutDir) - if err != nil { - log.Error().Err(err).Msg("Error generating YAML report") - return err - } - return nil + cmd.Execute() } diff --git a/scripts/bytecode-diff/templates/report.html b/scripts/bytecode-diff/templates/report.html new file mode 100644 index 000000000..acd743623 --- /dev/null +++ b/scripts/bytecode-diff/templates/report.html @@ -0,0 +1,118 @@ + + + + + + Base Facets Bytecode Diff Report + + + +

{{.environment}}: Base Facets Bytecode Diff Report

+

Generated on: {{.reportTime}}

+ +

Deployments

+ + + + + + + + + {{range $name, $deployment := .deployments}} + + + + + + + + {{end}} +
NameAddressBytecode HashBasescan LinkDeployment Date
{{$name}} + {{if eq $.environment "gamma"}} + {{$deployment.address}} + {{else if eq $.environment "omega"}} + {{$deployment.address}} + {{else}} + {{$deployment.address}} + {{end}} + {{$deployment.bytecodeHash}}View on Basescan{{$deployment.deploymentDate}}
+ +

Diamonds

+ {{if .diamonds}} + {{range $name, $diamond := .diamonds}} +

Diamond: {{$diamond.name}}

+

Facets

+ {{if $diamond.facets}} + + + + + + + + + + + + + {{range $facet := $diamond.facets}} + + + + + + + + + + + {{end}} +
Contract NameOrigin AddressOrigin Bytecode HashTarget AddressesTarget Bytecode HashesSelectors Missing on {{$.environment}}Origin VerifiedTarget Verified
{{$facet.originContractName}} + {{if eq $.environment "gamma"}} + {{$facet.originFacetAddress}} + {{else if eq $.environment "omega"}} + {{$facet.originFacetAddress}} + {{else}} + {{$facet.originFacetAddress}} + {{end}} + {{$facet.originBytecodeHash}} + {{range $addr := $facet.targetContractAddresses}} + {{if eq $.environment "gamma"}} + {{$addr}}
+ {{else if eq $.environment "omega"}} + {{$addr}}
+ {{else}} + {{$addr}}
+ {{end}} + {{end}} +
+ {{range $hash := $facet.targetBytecodeHashes}} + {{$hash}}
+ {{end}} +
{{$facet.selectorsDiff}}{{$facet.originVerified}}{{$facet.targetVerified}}
+ {{else}} +

No facets found for this diamond.

+ {{end}} + {{end}} + {{else}} +

No diamonds found in the YAML data.

+ {{end}} + + {{range $key, $value := .}} + {{if and (ne $key "deployments") (ne $key "diamonds")}} +

{{$key}}

+
{{$value | printf "%#v"}}
+ {{end}} + {{end}} + + \ No newline at end of file diff --git a/scripts/bytecode-diff/utils/ethereum.go b/scripts/bytecode-diff/utils/ethereum.go index a717dc731..225cd5e23 100644 --- a/scripts/bytecode-diff/utils/ethereum.go +++ b/scripts/bytecode-diff/utils/ethereum.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "strings" + "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -89,10 +90,16 @@ func ReadAllFacets(client *ethclient.Client, contractAddress string, basescanAPI } for i, facet := range facets { + // Throttle API calls to 2 per second to avoid being rate limited + time.Sleep(500 * time.Millisecond) + // read contract name from basescan source code api contractName, err := GetContractNameFromBasescan(basescanUrl, facet.FacetAddress.Hex(), basescanAPIKey) if err != nil { - return nil, fmt.Errorf("failed to get contract name from Basescan: %w", err) + return nil, fmt.Errorf( + "failed to get contract name from Basescan: %w", + err, + ) } facets[i].ContractName = contractName @@ -127,7 +134,13 @@ func ReadAllFacets(client *ethclient.Client, contractAddress string, basescanAPI return facets, nil } -func CreateEthereumClients(baseRpcUrl, baseSepoliaRpcUrl, originEnvironment, targetEnvironment string, verbose bool) (map[string]*ethclient.Client, error) { +func CreateEthereumClients( + baseRpcUrl string, + baseSepoliaRpcUrl string, + originEnvironment string, + targetEnvironment string, + verbose bool, +) (map[string]*ethclient.Client, error) { clients := make(map[string]*ethclient.Client) for _, env := range []string{originEnvironment, targetEnvironment} { @@ -180,11 +193,17 @@ func GetContractNameFromBasescan(baseURL, address, apiKey string) (string, error } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("Basescan API returned non-200 status code: %d", resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response body: %w", err) } + Log.Debug().Msgf("Raw Basescan JSON response: %s", string(body)) + var result struct { Status string `json:"status"` Message string `json:"message"` @@ -209,20 +228,26 @@ func GetContractNameFromBasescan(baseURL, address, apiKey string) (string, error return result.Result[0].ContractName, nil } +// GetContractCodeHash fetches the deployed code and calculates its keccak256 hash +func GetContractCodeHash(client *ethclient.Client, address common.Address) (string, error) { + code, err := client.CodeAt(context.Background(), address, nil) + if err != nil { + return "", fmt.Errorf("failed to read contract code for address %s: %w", address.Hex(), err) + } + + hash := crypto.Keccak256Hash(code) + return hash.Hex(), nil +} + // AddContractCodeHashes reads the contract code for each facet and adds its keccak256 hash to the Facet struct func AddContractCodeHashes(client *ethclient.Client, facets []Facet) error { for i, facet := range facets { - // Read the contract code - code, err := client.CodeAt(context.Background(), facet.FacetAddress, nil) + hash, err := GetContractCodeHash(client, facet.FacetAddress) if err != nil { - return fmt.Errorf("failed to read contract code for address %s: %w", facet.FacetAddress.Hex(), err) + return err } - // Hash the code using Keccak256Hash - hash := crypto.Keccak256Hash(code) - - // Store the hash hex string in the Facet struct - facets[i].BytecodeHash = hash.Hex() + facets[i].BytecodeHash = hash } return nil From b4518a4e679f9efa0d38356cac103dd5b1c913a6 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Mon, 9 Sep 2024 12:59:28 -0700 Subject: [PATCH 27/30] upgrade-facets bash script running make targets --- .../bytecode-diff/scripts/upgrade-facets.sh | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100755 scripts/bytecode-diff/scripts/upgrade-facets.sh diff --git a/scripts/bytecode-diff/scripts/upgrade-facets.sh b/scripts/bytecode-diff/scripts/upgrade-facets.sh new file mode 100755 index 000000000..f523a5902 --- /dev/null +++ b/scripts/bytecode-diff/scripts/upgrade-facets.sh @@ -0,0 +1,155 @@ +#!/bin/bash +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" +cd .. + +# Function to find the most recent file +find_most_recent_file() { + local dir="$1" + find "$dir" -name "facet_diff_*.yaml" | sort -r | head -n 1 +} + +# Check if the correct number of arguments is provided +if [ $# -lt 1 ] || [ $# -gt 3 ]; then + echo "Usage: $0 [diff_directory] [file_name]" + echo " : Must be either 'gamma' or 'omega'" + exit 1 +fi + +# Set the network and validate it +network="$1" +if [ "$network" != "gamma" ] && [ "$network" != "omega" ]; then + echo "Error: Network must be either 'gamma' or 'omega'" + exit 1 +fi + +# Set default values and parse additional arguments +diff_dir="deployed-diffs" +file_name="" + +if [ $# -eq 2 ]; then + diff_dir="$2" +elif [ $# -eq 3 ]; then + diff_dir="$2" + file_name="$3" +fi + +# If file_name is not provided, find the most recent file +if [ -z "$file_name" ]; then + most_recent_file=$(find_most_recent_file "$diff_dir") + if [ -n "$most_recent_file" ]; then + file_name=$(basename "$most_recent_file") + echo "Using most recent file: $file_name" + else + echo "No matching files found in $diff_dir" + exit 1 + fi +fi + +# Function to process a single YAML file +process_file() { + local file="$1" + echo "Processing file: $file" + + # Extract originContractNames into an array, strip "Facet" suffix, and remove duplicates + contract_names=($(yq e '.diamonds[].facets[].originContractName' "$file" | sed 's/Facet$//' | sort -u)) + + # Determine which make command to use + if [[ "$network" == "omega" ]]; then + chain_id=8453 + context="omega" + make_command="make deploy-base" + elif [[ "$network" == "gamma" ]]; then + chain_id=84532 + context="gamma" + make_command="make deploy-base-sepolia" + else + echo "Error: Unknown file type $file. Cannot determine chain ID and context." + exit 1 + fi + + # Loop through each contract name and call the appropriate make command + if [ ${#contract_names[@]} -eq 0 ]; then + echo "No contracts to deploy." + exit 0 + else + current_dir=$(pwd) + cd ../../contracts + for contract in "${contract_names[@]}"; do + deploy_file=$(find ./scripts -name "Deploy${contract}.s.sol" -o -name "Deploy${contract}Facet.s.sol" | head -n1) + + if [ -n "$deploy_file" ]; then + deploy_contract=$(basename "$deploy_file" .s.sol) + echo "Deploying contract: $contract using $deploy_contract to chain $chain_id with context $context" + OVERRIDE_DEPLOYMENTS=1 $make_command context="$context" type=facets contract="$deploy_contract" + + if [ $? -ne 0 ]; then + echo "Error deploying $contract" + fi + else + echo "Error: Deploy file not found for $contract. Skipping." + fi + done + cd "$current_dir" + fi + + # Call process_deployments after processing the file + process_deployments "$chain_id" "$file" "${contract_names[@]}" +} + +# Function to process deployments and create a new YAML file +process_deployments() { + local chain_id="$1" + local input_file="$2" + shift 2 + local contract_names=("$@") + + # Append a new deployments section to the input file + echo -e "\ndeployments:" >> "$input_file" + + for contract in "${contract_names[@]}"; do + local json_file="../../broadcast/Deploy${contract}.s.sol/${chain_id}/run-latest.json" + if [[ -f "$json_file" ]]; then + local contract_name=$(jq -r '.transactions[0].contractName' "$json_file") + local contract_address=$(jq -r '.transactions[0].contractAddress' "$json_file") + local tx_hash=$(jq -r '.transactions[0].hash' "$json_file") + local deployment_date=$(date -r "$json_file" -u +"%Y-%m-%d %H:%M") + + # Determine the baseScanLink based on the chain_id + if [ "$chain_id" == "84532" ]; then + local base_scan_link="https://sepolia.basescan.org/tx/$tx_hash" + elif [ "$chain_id" == "8453" ]; then + local base_scan_link="https://basescan.org/tx/$tx_hash" + else + local base_scan_link="" + fi + + # Append deployment information for each contract + echo " $contract_name:" >> "$input_file" + echo " address: $contract_address" >> "$input_file" + echo " transactionHash: $tx_hash" >> "$input_file" + echo " deploymentDate: $deployment_date" >> "$input_file" + echo " bytecodeHash: " >> "$input_file" + if [ -n "$base_scan_link" ]; then + echo " baseScanLink: $base_scan_link" >> "$input_file" + fi + fi + done + + echo "Deployment information appended to $input_file" +} + +# Main script +if [ -n "$file_name" ]; then + if [[ -f "$diff_dir/$file_name" ]]; then + process_file "$diff_dir/$file_name" + else + echo "Error: Specified file $diff_dir/$file_name not found." + exit 1 + fi +else + for file in "$diff_dir"/diff_*.yaml; do + if [[ -f "$file" ]]; then + process_file "$file" + fi + done +fi \ No newline at end of file From 04b0d449ff2b1238ef7f2c358dd4615bfd5403f9 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Mon, 9 Sep 2024 13:16:05 -0700 Subject: [PATCH 28/30] remove unused workflow --- .../workflows/Render_diff_upgrade_report.yml | 91 ------------------- 1 file changed, 91 deletions(-) delete mode 100644 .github/workflows/Render_diff_upgrade_report.yml diff --git a/.github/workflows/Render_diff_upgrade_report.yml b/.github/workflows/Render_diff_upgrade_report.yml deleted file mode 100644 index 33a358186..000000000 --- a/.github/workflows/Render_diff_upgrade_report.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Render Diff Upgrade Report -env: - BASE_RPC_URL: ${{ secrets.BASE_RPC_URL }} - BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }} - REPORT_OUT_DIR: ${{ vars.REPORT_OUT_DIR }} - AWS_ACCESS_KEY_ID: ${{ secrets.RIVER_S3_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.RIVER_S3_AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.RIVER_S3_AWS_REGION }} - GOWORK: off - -on: - workflow_dispatch: - inputs: - environment_name: - description: 'Environment name' - required: true - type: choice - options: - - 'alpha' - - 'gamma' - - 'omega' - - workflow_call: - inputs: - environment_name: - description: 'Environment name' - required: true - type: string - -jobs: - Render_Diff_Upgrade_Report: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.22' # Specify the Go version you need - - - name: Install dependencies - run: | - go mod download - go mod tidy - working-directory: scripts/bytecode-diff - - - name: Get latest facet diff file - run: | - FACET_DIFF_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "facet_diff*.yaml" | sort -r | head -n 1) - echo "Latest facet diff file: $FACET_DIFF_FILE" - echo "FACET_DIFF_FILE=$FACET_DIFF_FILE" >> $GITHUB_ENV - - - name: Run final html report generation w/ updated hashes - id: gen_html_report - run: | - go run main.go add-hashes ${{ inputs.environment_name }} ${{ env.FACET_DIFF_FILE }} - working-directory: scripts/bytecode-diff - - - name: Configure AWS credentials - if: steps.gen_html_report.outcome == 'success' - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Copy HTML report to S3 - if: steps.gen_html_report.outcome == 'success' - run: | - # Find the latest hashed HTML file - HTML_FILE=$(find ${{ env.REPORT_OUT_DIR }} -name "*_hashed_*.html" | sort -r | head -n 1) - - if [ -z "$HTML_FILE" ]; then - echo "Error: No hashed HTML file found" - exit 1 - fi - - echo "Latest hashed HTML file: $HTML_FILE" - - # Copy to report.html - aws s3 cp $HTML_FILE s3://${{ inputs.environment_name }}-reports/report.html - - # Copy to report_YYYYMMDD.html - DATED_FILENAME="report_$(date -u +%Y%m%d).html" - aws s3 cp $HTML_FILE s3://${{ inputs.environment_name }}-reports/$DATED_FILENAME - - echo "HTML report copied to S3 bucket ${{ inputs.environment_name }}-reports files report.html and $DATED_FILENAME" From fb23f54a689d7aa2f02eed4e9cb74592c2362de1 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Mon, 9 Sep 2024 13:26:27 -0700 Subject: [PATCH 29/30] add cron schedule initially for every monday morning 6 am UTC --- .github/workflows/Network_diff_upgrade.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index c58aee7d2..8ce57e93a 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -2,6 +2,8 @@ name: Network Diff Upgrade on: workflow_dispatch: + schedule: + - cron: '0 6 * * 1' # Run every Monday at 6:00 AM UTC jobs: Run_Alpha_to_Gamma: From f49c237f3aeea420be091f04670b9d9129a50bd2 Mon Sep 17 00:00:00 2001 From: John Terzis Date: Mon, 9 Sep 2024 16:33:41 -0700 Subject: [PATCH 30/30] prettier fixes --- .github/workflows/Network_diff_upgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Network_diff_upgrade.yml b/.github/workflows/Network_diff_upgrade.yml index 8ce57e93a..a111588c0 100644 --- a/.github/workflows/Network_diff_upgrade.yml +++ b/.github/workflows/Network_diff_upgrade.yml @@ -3,7 +3,7 @@ name: Network Diff Upgrade on: workflow_dispatch: schedule: - - cron: '0 6 * * 1' # Run every Monday at 6:00 AM UTC + - cron: '0 6 * * 1' # Run every Monday at 6:00 AM UTC jobs: Run_Alpha_to_Gamma: